Message ID | 8ecfad5ef5412df784a96707969129913f44a351.1736868948.git.convivo@nibeconsultants.eu |
---|---|
State | Accepted |
Headers | show |
Series | handler: make NAND flashing streamable | expand |
Hi Viacheslav Volkov, Regarding treatment of -EIO upon erase or write operations, let's treat MTD UBI code as the "industry standard". Here is the Linux upstream UBI implementation: https://github.com/torvalds/linux/tree/master/drivers/mtd/ubi In this code, there are cases where an -EIO upon erase (maybe write also, can't remember) does not automatically mark the block as bad but instead allow the block to be "tortured" to see if the EIO error was some odd temporary issue (bus issue? NAND memory temporary condition?) or permanent. In a device I was involved with in the past, a particular NAND vendor had NAND memory problems due to the manufacturing process errors that cause "false" temporary EIO errors to be generated, but the block was not permanently bad. So the UBI torture test capability was useful for to save these devices (millions of devices) from failure due running out of spare blocks from marking blocks bad on the first EIO error return. Given that explanation, can you provide the caller with an option to pass it the MTD error and let the caller retry and mark bad as they see fit? Thanks, Rob On Tuesday, January 14, 2025 at 9:36:40 AM UTC-6 Viacheslav Volkov wrote: > Signed-off-by: Viacheslav Volkov <con...@nibeconsultants.eu> > Signed-off-by: Måns Andersson <mans.an...@nibe.se> > --- > Changes in v2: > 1. Move unit tests to a dedicated patch > 2. Delete EMPTY_BYTE definition, use FLASH_EMPTY_BYTE from flash.h > instead. > --- > .gitignore | 1 + > configs/test_defconfig | 2 + > test/Makefile | 1 + > test/test_flash_handler.c | 1456 +++++++++++++++++++++++++++++++++++++ > 4 files changed, 1460 insertions(+) > create mode 100644 test/test_flash_handler.c > > diff --git a/.gitignore b/.gitignore > index 4755ff07..313a8b55 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -52,6 +52,7 @@ tools/*.out > test/test_crypt > test/test_hash > test/test_json > +test/test_flash_handler > test/test_server_hawkbit > test/test_util > test/test_verify > diff --git a/configs/test_defconfig b/configs/test_defconfig > index ac8fff6d..5fef117b 100644 > --- a/configs/test_defconfig > +++ b/configs/test_defconfig > @@ -14,3 +14,5 @@ CONFIG_LUASCRIPTHANDLER=y > CONFIG_RAW=y > CONFIG_REMOTE_HANDLER=y > CONFIG_SHELLSCRIPTHANDLER=y > +CONFIG_MTD=y > +CONFIG_CFI=y > diff --git a/test/Makefile b/test/Makefile > index ad26b40d..6f3da093 100644 > --- a/test/Makefile > +++ b/test/Makefile > @@ -27,6 +27,7 @@ endif > tests-$(CONFIG_SURICATTA_HAWKBIT) += test_json > tests-$(CONFIG_SURICATTA_HAWKBIT) += test_server_hawkbit > tests-y += test_util > +tests-$(CONFIG_CFI) += test_flash_handler > > ccflags-y += -I$(src)/../ > > diff --git a/test/test_flash_handler.c b/test/test_flash_handler.c > new file mode 100644 > index 00000000..56264558 > --- /dev/null > +++ b/test/test_flash_handler.c > @@ -0,0 +1,1456 @@ > +/* > + * Author: Viacheslav Volkov > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include <stddef.h> > +#include <stdarg.h> > +#include <setjmp.h> > +#include <cmocka.h> > + > +#include <stdint.h> > +#include <libmtd.h> > +#include <mtd/mtd-user.h> > + > +#include <stdbool.h> > +#include <stdio.h> > +#include <string.h> > +#include <fcntl.h> > +#include <unistd.h> > +#include <errno.h> > +#include <limits.h> > + > +#include "util.h" > +#include "pctl.h" > +#include "flash.h" > +#include "handler.h" > +#include "swupdate_image.h" > +#include "swupdate_status.h" > + > +/* #define PRINT_LOGS */ > +#define RETURN_CODE_ENOSPC > +#define DEV_MTD_PREFIX "/dev/mtd" > +#define MTD_DEV_IDX 0 > +/* MTD_FD is a big dummy value: it is very unlikely for us to have so > many open > + * file descriptors: */ > +#define MTD_FD 999999 > +#define LIBMTD_T_VALUE ((libmtd_t)0x12345678) /* dummy value */ > + > +#define FOREACH_PAGE_IN_EB(page, mtd, eb) \ > + for (page = (eb) * ((mtd)->eb_size / (mtd)->min_io_size); \ > + page < (eb + 1) * ((mtd)->eb_size / (mtd)->min_io_size); \ > + page++) > + > +#define FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) \ > + for (page = ((eb) * (mtd)->eb_size + (offs)) / (mtd)->min_io_size; \ > + page < ((eb) * (mtd)->eb_size + (offs) + (len)) / (mtd)->min_io_size; \ > + page++) > + > +/* > + * Mock data > + */ > +static struct img_type image = { > + .type = "flash", > +}; > +static struct mtd_ubi_info mtd_ubi_info_s; > + > +/* > + * Test-only data > + */ > +static handler handler_func; > +static bool is_nand; > +static unsigned char *image_buf; > +static void (*patch_image_buf)(unsigned char *buf); > +static int eb_bytes; > +static int pages_bytes; > +/* Actual flash state before test run: */ > +static unsigned char *bad_blocks; > +static unsigned char *locked_blocks; > +static unsigned char *written_pages; > +static unsigned char *flash_memory; > +/* Expected flash state after test run: */ > +static unsigned char *expected_bad_blocks; > +static unsigned char *expected_locked_blocks; > +static unsigned char *expected_written_pages; > +static unsigned char *expected_flash_memory; > + > +void *__real_malloc(size_t size); > +static void *(*impl_malloc)(size_t size) = __real_malloc; > +void *__wrap_malloc(size_t size); > +void *__wrap_malloc(size_t size) > +{ > + return impl_malloc(size); > +} > + > +int __real_open(const char *pathname, int flags); > +static int default_open(const char *pathname, int flags) > +{ > + int ret; > + if (strcmp(pathname, image.device) == 0) > + return MTD_FD; > + ret = __real_open(pathname, flags); > + /* Usually file descriptors start from 0. Since MTD_FD is big enough, > + * a collision is very unlikely. */ > + assert_int_not_equal(ret, MTD_FD); > + return ret; > +} > +static int (*impl_open)(const char *pathname, int flags) = default_open; > +int __wrap_open(const char *pathname, int flags); > +int __wrap_open(const char *pathname, int flags) > +{ > + return impl_open(pathname, flags); > +} > + > +int __real_close(int fd); > +int __wrap_close(int fd); > +int __wrap_close(int fd) > +{ > + if (fd == MTD_FD) > + return 0; > + return __real_close(fd); > +} > + > +off_t __real_lseek(int fd, off_t offset, int whence); > +off_t __wrap_lseek(int fd, off_t offset, int whence); > +off_t __wrap_lseek(int fd, off_t offset, int whence) > +{ > + if (fd == MTD_FD) { > + /* __swupdate_copy() executes lseek() on /dev/mtdX. */ > + assert_int_equal(image.seek, offset); > + assert_int_equal(SEEK_SET, whence); > + return offset; > + } > + return __real_lseek(fd, offset, whence); > +} > + > +int __wrap_mtd_dev_present(libmtd_t desc, int mtd_num); > +int __wrap_mtd_dev_present(libmtd_t UNUSED desc, int mtd_num) > +{ > + assert_int_equal(MTD_DEV_IDX, mtd_num); > + return 1; > +} > + > +int __wrap_get_mtd_from_device(char *s); > +int __wrap_get_mtd_from_device(char *s) > +{ > + int ret, mtdnum; > + assert_ptr_not_equal(NULL, s); > + if (strlen(s) < sizeof(DEV_MTD_PREFIX)) > + return -1; > + ret = sscanf(s, DEV_MTD_PREFIX "%d", &mtdnum); > + if (ret <= 0) > + return -1; > + return mtdnum; > +} > + > +static unsigned char get_byte_idx(int bit_idx) > +{ > + return bit_idx / CHAR_BIT; > +} > + > +static unsigned char get_mask(int bit_idx) > +{ > + return 1 << (bit_idx % CHAR_BIT); > +} > + > +static bool get_bit(unsigned char *data, int bit_idx) > +{ > + return (data[get_byte_idx(bit_idx)] & get_mask(bit_idx)) != 0; > +} > + > +static void set_bit(unsigned char *data, int bit_idx) > +{ > + data[get_byte_idx(bit_idx)] |= get_mask(bit_idx); > +} > + > +static void clear_bit(unsigned char *data, int bit_idx) > +{ > + data[get_byte_idx(bit_idx)] &= (~get_mask(bit_idx)); > +} > + > +static void set_multiple_bits(unsigned char *data, const int *bit_indices) > +{ > + int bit_idx = bit_indices[0]; > + for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i]) > + set_bit(data, bit_idx); > +} > + > +static void clear_multiple_bits(unsigned char *data, const int > *bit_indices) > +{ > + int bit_idx = bit_indices[0]; > + for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i]) > + clear_bit(data, bit_idx); > +} > + > +static void check_args(const struct mtd_dev_info *mtd, int fd, int eb) > +{ > + assert_ptr_equal(&mtd_ubi_info_s.mtd, mtd); > + assert_true( > + (mtd->type == MTD_NORFLASH) || > + (mtd->type == MTD_NANDFLASH)); > + assert_int_equal(MTD_FD, fd); > + assert_true(eb >= 0); > + assert_true(eb < mtd->size / mtd->eb_size); > +} > + > +static int default_mtd_get_bool(const struct mtd_dev_info UNUSED *mtd, > + int UNUSED fd, int eb, unsigned char *data) > +{ > + return get_bit(data, eb) ? 1 : 0; > +} > + > +static int default_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int > eb) > +{ > + return default_mtd_get_bool(mtd, fd, eb, bad_blocks); > +} > +static int (*impl_mtd_is_bad)(const struct mtd_dev_info *mtd, int fd, int > eb); > +int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb); > +int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb) > +{ > + check_args(mtd, fd, eb); > + return impl_mtd_is_bad(mtd, fd, eb); > +} > + > +static int default_mtd_mark_bad(const struct mtd_dev_info UNUSED *mtd, > + int UNUSED fd, int eb) > +{ > + set_bit(bad_blocks, eb); > + return 0; > +} > +static int (*impl_mtd_mark_bad)(const struct mtd_dev_info *mtd, int fd, > + int eb); > +int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb); > +int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb) > +{ > + check_args(mtd, fd, eb); > + assert_false(get_bit(bad_blocks, eb)); > + /* Note: we don't require erasing a block before marking it bad. */ > + return impl_mtd_mark_bad(mtd, fd, eb); > +} > + > +static int default_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, > + int eb) > +{ > + return default_mtd_get_bool(mtd, fd, eb, locked_blocks); > +} > +static int (*impl_mtd_is_locked)(const struct mtd_dev_info *mtd, int fd, > + int eb); > +int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb); > +int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb) > +{ > + check_args(mtd, fd, eb); > + return impl_mtd_is_locked(mtd, fd, eb); > +} > + > +static int default_mtd_unlock(const struct mtd_dev_info UNUSED *mtd, > + int UNUSED fd, int eb) > +{ > + clear_bit(locked_blocks, eb); > + return 0; > +} > +static int (*impl_mtd_unlock)(const struct mtd_dev_info *mtd, int fd, int > eb); > +int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb); > +int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb) > +{ > + check_args(mtd, fd, eb); > + assert_false(get_bit(bad_blocks, eb)); > + /* Unlocking already unlocked blocks is totaly fine: > + * - not every flash supports mtd_is_locked() check; > + * - some implementation might prefer to mtd_unlock() rigth away > + * without checking mtd_is_locked(). */ > + return impl_mtd_unlock(mtd, fd, eb); > +} > + > +static int default_mtd_erase(libmtd_t UNUSED desc, > + const struct mtd_dev_info *mtd, int UNUSED fd, int eb) > +{ > + unsigned int page; > + memset(flash_memory + eb * mtd->eb_size, FLASH_EMPTY_BYTE, > + mtd->eb_size); > + FOREACH_PAGE_IN_EB(page, mtd, eb) > + clear_bit(written_pages, page); > + return 0; > +} > +static int (*impl_mtd_erase)(libmtd_t desc, const struct mtd_dev_info > *mtd, > + int fd, int eb); > +int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int > fd, > + int eb); > +int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int > fd, > + int eb) > +{ > + check_args(mtd, fd, eb); > + assert_false(get_bit(bad_blocks, eb)); > + assert_false(get_bit(locked_blocks, eb)); > + return impl_mtd_erase(desc, mtd, fd, eb); > +} > + > +static int default_mtd_write(libmtd_t UNUSED desc, > + const struct mtd_dev_info *mtd, int UNUSED fd, int eb, > + int offs, void *data, int len, void UNUSED *oob, > + int UNUSED ooblen, uint8_t UNUSED mode) > +{ > + unsigned int page; > + unsigned int flash_offset = eb * mtd->eb_size + offs; > + const unsigned char *pdata = (const unsigned char*)data; > + FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) { > + set_bit(written_pages, page); > + memcpy(flash_memory + flash_offset, pdata, mtd->min_io_size); > + flash_offset += mtd->min_io_size; > + pdata += mtd->min_io_size; > + } > + return 0; > +} > + > +static int (*impl_mtd_write)(libmtd_t desc, const struct mtd_dev_info > *mtd, > + int fd, int eb, int offs, void *data, int len, void *oob, > + int ooblen, uint8_t mode); > +int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int > fd, > + int eb, int offs, void *data, int len, void *oob, int ooblen, > + uint8_t mode); > + > +int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int > fd, > + int eb, int offs, void *data, int len, void *oob, int ooblen, > + uint8_t mode) > +{ > + unsigned int page; > + check_args(mtd, fd, eb); > + assert_true(offs >= 0); > + assert_ptr_not_equal(NULL, data); > + assert_true(len > 0); > + assert_true(offs + len <= mtd->eb_size); > + assert_ptr_equal(NULL, oob); > + assert_int_equal(0, ooblen); > + assert_false(get_bit(bad_blocks, eb)); > + assert_false(get_bit(locked_blocks, eb)); > + assert_true(offs <= mtd->eb_size - mtd->min_io_size); > + assert_int_equal(0, offs % mtd->min_io_size); > + assert_int_equal(0, len % mtd->min_io_size); > + > + if (mtd->type == MTD_NANDFLASH) { > + /* Follow "write once rule" for NAND flash. */ > + FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) > + assert_false(get_bit(written_pages, page)); > + } else { > + /* Assume it is ok to write multiple times to NOR flash. */ > + unsigned int flash_i = eb * mtd->eb_size + offs; > + for (int data_i = 0; data_i < len; data_i++) { > + uint8_t old_byte = flash_memory[flash_i + data_i]; > + uint8_t new_byte = ((uint8_t*)data)[data_i]; > + assert_true((old_byte & new_byte) == new_byte); > + } > + } > + return impl_mtd_write(desc, mtd, fd, eb, offs, data, len, oob, ooblen, > + mode); > +} > + > +static int default_mtd_read(const struct mtd_dev_info *mtd, int fd, int > eb, > + int offs, void *buf, int len) > +{ > + unsigned int flash_offset = eb * mtd->eb_size + offs; > + check_args(mtd, fd, eb); > + memcpy(buf, flash_memory + flash_offset, len); > + return 0; > +} > +static int (*impl_mtd_read)(const struct mtd_dev_info *mtd, int fd, int > eb, > + int offs, void *buf, int len); > +int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int > offs, > + void *buf, int len); > +int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int > offs, > + void *buf, int len) > +{ > + check_args(mtd, fd, eb); > + assert_true(offs >= 0); > + assert_ptr_not_equal(NULL, buf); > + assert_true(len > 0); > + assert_true(offs + len <= mtd->eb_size); > + assert_false(get_bit(bad_blocks, eb)); > + assert_false(get_bit(locked_blocks, eb)); > + assert_true(offs <= mtd->eb_size - mtd->min_io_size); > + assert_int_equal(0, offs % mtd->min_io_size); > + assert_int_equal(0, len % mtd->min_io_size); > + return impl_mtd_read(mtd, fd, eb, offs, buf, len); > +} > + > +static unsigned char *generate_image_file(const char *name, size_t size) > +{ > + int r, fd; > + unsigned char *buf = malloc(size); > + unsigned char *pbuf = buf; > + assert_ptr_not_equal(NULL, buf); > + fd = open(name, O_WRONLY); > + if (fd < 0) > + free(buf); > + assert_true(fd >= 0); > + > + for (size_t i = 0; i < size; i++) > + buf[i] = (unsigned char)i; > + > + if (patch_image_buf) > + patch_image_buf(buf); > + > + while (size > 0) { > + ssize_t ret = write(fd, pbuf, size); > + if (ret < 0) { > + close(fd); > + free(buf); > + } > + assert_true(ret >= 0); > + pbuf += ret; > + size -= ret; > + } > + > + r = close(fd); > + assert_int_equal(0, r); > + return buf; > +} > + > +static int group_setup(void UNUSED **state) > +{ > + int ret, fd; > + struct flash_description *flash = get_flash_info(); > + struct installer_handler *hnd = find_handler(&image); > + > + assert_ptr_not_equal(NULL, hnd); > + handler_func = hnd->installer; > + assert_ptr_not_equal(NULL, handler_func); > + flash->mtd_info = &mtd_ubi_info_s; > + flash->libmtd = LIBMTD_T_VALUE; > + > +#ifdef PRINT_LOGS > + loglevel = DEBUGLEVEL; > + notify_init(); > +#endif > + > + strcpy(image.fname, "swupdate_image_XXXXXX.bin"); > + fd = mkstemps(image.fname, 4 /* = strlen(".bin") */); > + if (fd < 0) > + image.fname[0] = 0; /* required for group_teardown() */ > + ret = close(fd); > + assert_int_equal(0, ret); > + return 0; > +} > + > +static int group_teardown(void UNUSED **state) > +{ > + if (image.fname[0] != 0) { > + int ret = unlink(image.fname); > + assert_int_equal(0, ret); > + } > + return 0; > +} > + > +static void copy_flash_state(void) > +{ > + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; > + > + expected_bad_blocks = malloc(eb_bytes); > + assert_ptr_not_equal(NULL, expected_bad_blocks); > + memcpy(expected_bad_blocks, bad_blocks, eb_bytes); > + > + expected_locked_blocks = malloc(eb_bytes); > + assert_ptr_not_equal(NULL, expected_locked_blocks); > + memcpy(expected_locked_blocks, locked_blocks, eb_bytes); > + > + expected_written_pages = malloc(pages_bytes); > + assert_ptr_not_equal(NULL, expected_written_pages); > + memcpy(expected_written_pages, written_pages, pages_bytes); > + > + expected_flash_memory = malloc(mtd->size); > + assert_ptr_not_equal(NULL, expected_flash_memory); > + memcpy(expected_flash_memory, flash_memory, mtd->size); > +} > + > +static void init_flash_state(void) > +{ > + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; > + int num = (int)(mtd->size / mtd->eb_size); > + eb_bytes = num / CHAR_BIT; > + if (num % CHAR_BIT) > + eb_bytes++; > + /* By default there are no any bad blocks: */ > + bad_blocks = calloc(eb_bytes, 1); > + assert_ptr_not_equal(NULL, bad_blocks); > + /* By default all blocks are locked: */ > + locked_blocks = malloc(eb_bytes); > + assert_ptr_not_equal(NULL, locked_blocks); > + memset(locked_blocks, 0xFF, eb_bytes); > + > + num = (int)(mtd->size / mtd->min_io_size); > + pages_bytes = num / CHAR_BIT; > + if (num % CHAR_BIT) > + pages_bytes++; > + /* By default all pages are written (require erase): */ > + written_pages = malloc(pages_bytes); > + assert_ptr_not_equal(NULL, written_pages); > + memset(written_pages, 0xFF, pages_bytes); > + > + flash_memory = malloc(mtd->size); > + assert_ptr_not_equal(NULL, flash_memory); > + /* Fill flash memory with initial pattern: */ > + memset(flash_memory, 0xA5, mtd->size); > +} > + > +static void test_init(void) > +{ > + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; > + > + /* Unit test config (precondition) sanity check: */ > + assert_true(mtd->size >= mtd->eb_size); > + assert_true(mtd->eb_size >= mtd->min_io_size); > + assert_int_equal(0, mtd->size % mtd->eb_size); > + assert_int_equal(0, mtd->eb_size % mtd->min_io_size); > + > + is_nand = (mtd->type == MTD_NANDFLASH) || > + (mtd->type == MTD_MLCNANDFLASH); > + image_buf = generate_image_file(image.fname, image.size); > + image.fdin = open(image.fname, O_RDONLY); > + assert_true(image.fdin >= 0); > + > + init_flash_state(); > +} > + > +static void verify_flash_state(void) > +{ > + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; > + assert_memory_equal(expected_bad_blocks, bad_blocks, eb_bytes); > + assert_memory_equal(expected_locked_blocks, locked_blocks, eb_bytes); > + assert_memory_equal(expected_written_pages, written_pages, > + pages_bytes); > + assert_memory_equal(expected_flash_memory, flash_memory, mtd->size); > +} > + > +static void run_flash_test(void UNUSED **state, int expected_return_code) > +{ > + int ret = handler_func(&image, NULL); > +#ifdef RETURN_CODE_ENOSPC > + /* Currently on callback() failure __swupdate_copy() always returns > + * -ENOSPC (no matter what callback() returned). Hence some subset of > + * flash handler return values are converted to -ENOSPC. Here we'll > + * convert rest of return values to -ENOSPC to map expected and actual > + * return values to a simple success/failure check. */ > + if (ret < 0) > + ret = -ENOSPC; > + if (expected_return_code < 0) > + expected_return_code = -ENOSPC; > +#endif > + assert_int_equal(expected_return_code, ret); > + verify_flash_state(); > +} > + > +static int test_setup(void UNUSED **state) > +{ > + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; > + image_buf = NULL; > + patch_image_buf = NULL; > + image.fdin = -1; > + > + bad_blocks = NULL; > + locked_blocks = NULL; > + written_pages = NULL; > + flash_memory = NULL; > + > + expected_bad_blocks = NULL; > + expected_locked_blocks = NULL; > + expected_written_pages = NULL; > + expected_flash_memory = NULL; > + > + /* Tests can overwrite the following default implementations to inject > + * errors: */ > + impl_malloc = __real_malloc; > + impl_open = default_open; > + impl_mtd_is_bad = default_mtd_is_bad; > + impl_mtd_mark_bad = default_mtd_mark_bad; > + impl_mtd_is_locked = default_mtd_is_locked; > + impl_mtd_unlock = default_mtd_unlock; > + impl_mtd_erase = default_mtd_erase; > + impl_mtd_write = default_mtd_write; > + impl_mtd_read = default_mtd_read; > + > + /* Some default values: */ > + mtd->type = MTD_NANDFLASH; > + strcpy(image.device, DEV_MTD_PREFIX STR(MTD_DEV_IDX)); > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + return 0; > +} > + > +static int test_teardown(void UNUSED **state) > +{ > + unsigned char *pointer[] = { > + bad_blocks, > + locked_blocks, > + written_pages, > + flash_memory, > + > + expected_bad_blocks, > + expected_locked_blocks, > + expected_written_pages, > + expected_flash_memory, > + > + image_buf, > + }; > + impl_malloc = __real_malloc; > + for (int i = 0; i < ARRAY_SIZE(pointer); i++) > + free(pointer[i]); > + if (image.fdin >= 0) { > + int ret = close(image.fdin); > + assert_int_equal(0, ret); > + } > + return 0; > +} > + > +static void test_simple(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_simple_NOR(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 1; > + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_padding_less_than_page(void **state) > +{ > + image.seek = 0; > + image.size = 42; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + memset(expected_flash_memory + 42, FLASH_EMPTY_BYTE, 6); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_padding_page(void **state) > +{ > + image.seek = 0; > + image.size = 40; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + memset(expected_flash_memory + 40, FLASH_EMPTY_BYTE, 8); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 5); > + > + run_flash_test(state, 0); > +} > + > +static void patch_image_buf_empty_bytes_1(unsigned char *buf) > +{ > + memset(buf + 8, FLASH_EMPTY_BYTE, 24); > +} > + > +static void test_skip_write_page_empty_bytes(void **state) > +{ > + image.seek = 0; > + image.size = 40; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + patch_image_buf = patch_image_buf_empty_bytes_1; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + memset(expected_flash_memory + 40, FLASH_EMPTY_BYTE, 8); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + if (is_nand) > + clear_bit(expected_written_pages, 1); > + clear_bit(expected_written_pages, 2); > + clear_bit(expected_written_pages, 3); > + clear_bit(expected_written_pages, 5); > + > + run_flash_test(state, 0); > +} > + > +static void test_padding_more_than_page(void **state) > +{ > + image.seek = 0; > + image.size = 37; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + memset(expected_flash_memory + 37, FLASH_EMPTY_BYTE, 11); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 5); > + > + run_flash_test(state, 0); > +} > + > +static void test_seek(void **state) > +{ > + image.seek = 16; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory + 16, image_buf, image.size); > + for (int i = 1; i <= 3; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_seek_not_multiple_of_eb_size(void **state) > +{ > + image.seek = 8; > + mtd_ubi_info_s.mtd.eb_size = 16; > + test_init(); > + copy_flash_state(); > + run_flash_test(state, -EINVAL); > +} > + > +static void test_not_enough_flash(void **state) > +{ > + image.seek = 16; > + image.size = 1020; > + mtd_ubi_info_s.mtd.size = 1024; > + test_init(); > + copy_flash_state(); > + run_flash_test(state, -ENOSPC); > +} > + > +static void test_invalid_mtd_device(void **state) > +{ > + test_init(); > + strcpy(image.device, "/dev/mtdX"); > + copy_flash_state(); > + run_flash_test(state, -EINVAL); > +} > + > +static void test_invalid_image_size(void **state) > +{ > + test_init(); > + image.size = -42; > + copy_flash_state(); > + run_flash_test(state, -42); > +} > + > +static void test_empty_image(void **state) > +{ > + test_init(); > + image.size = 0; > + copy_flash_state(); > + run_flash_test(state, 0); > +} > + > +static int open_mtd_dev_failure_errno; > +static int open_mtd_dev_failure(const char *pathname, int flags) > +{ > + if (strcmp(pathname, image.device) == 0) { > + errno = open_mtd_dev_failure_errno; > + return -1; > + } > + return default_open(pathname, flags); > +} > +static void test_mtd_dev_open_failure(void **state) > +{ > + test_init(); > + impl_open = open_mtd_dev_failure; > + open_mtd_dev_failure_errno = EPERM; > + copy_flash_state(); > + run_flash_test(state, -open_mtd_dev_failure_errno); > +} > + > +static int malloc_filebuf_allocation_failure_size; > +static void *malloc_filebuf_allocation_failure(size_t size) > +{ > + if (size == malloc_filebuf_allocation_failure_size) { > + errno = ENOMEM; > + return NULL; > + } > + return __real_malloc(size); > +} > +static void test_malloc_failure(void **state) > +{ > + test_init(); > + copy_flash_state(); > + impl_malloc = malloc_filebuf_allocation_failure; > + malloc_filebuf_allocation_failure_size = mtd_ubi_info_s.mtd.eb_size; > + if (!is_nand) { > + /* filebuf allocation includes space for readout buffer */ > + malloc_filebuf_allocation_failure_size *= 2; > + } > + run_flash_test(state, -ENOMEM); > +} > + > +static void test_skip_bad_blocks(void **state) > +{ > + image.seek = 896; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + /* Initial flash state: | flash memory ends here > + * eb 56 57 58 59 60 61 62 63 | 64 > + * offset 896 912 928 944 960 976 992 1008 | 1024 > + * status bad ok bad ok bad bad ok bad | */ > + > + { > + int indices[] = {56, 58, 60, 61, 63, -1}; > + set_multiple_bits(bad_blocks, indices); > + } > + copy_flash_state(); > + > + { > + int indices[] = {57, 59, 62, -1}; > + clear_multiple_bits(expected_locked_blocks, indices); > + } > + memcpy(expected_flash_memory + 912, image_buf , 16); > + memcpy(expected_flash_memory + 944, image_buf + 16, 16); > + memcpy(expected_flash_memory + 992, image_buf + 32, 16); > + > + run_flash_test(state, 0); > +} > + > +static void test_too_many_known_bad_blocks(void **state) > +{ > + image.seek = 896; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + /* Initial flash state: | flash memory ends here > + * eb 56 57 58 59 60 61 62 63 | 64 > + * offset 896 912 928 944 960 976 992 1008 | 1024 > + * status bad ok bad ok bad bad bad bad | */ > + > + { > + int indices[] = {56, 58, 60, 61, 62, 63, -1}; > + set_multiple_bits(bad_blocks, indices); > + } > + copy_flash_state(); > + > + { > + int indices[] = {57, 59, -1}; > + clear_multiple_bits(expected_locked_blocks, indices); > + } > + memcpy(expected_flash_memory + 912, image_buf , 16); > + memcpy(expected_flash_memory + 944, image_buf + 16, 16); > + > + run_flash_test(state, -ENOSPC); > +} > + > +static int mtd_write_failure_1_eb; > +static int mtd_write_failure_1_offs; > +static int mtd_write_failure_1_errno; > +static int mtd_write_failure_1(libmtd_t desc, const struct mtd_dev_info > *mtd, > + int fd, int eb, int offs, void *data, int len, void *oob, > + int ooblen, uint8_t mode) > +{ > + if (eb == mtd_write_failure_1_eb) { > + unsigned char *p_data = (unsigned char *)data; > + for (int o = offs; o < offs + len; o += mtd->min_io_size) { > + int ret; > + if (o == mtd_write_failure_1_offs) { > + errno = mtd_write_failure_1_errno; > + return -1; > + } > + ret = default_mtd_write(desc, mtd, fd, eb, o, > + p_data, mtd->min_io_size, oob, > + ooblen, mode); > + assert_int_equal(0, ret); > + p_data += mtd->min_io_size; > + } > + } > + return default_mtd_write(desc, mtd, fd, eb, offs, data, len, oob, > + ooblen, mode); > +} > + > +static void test_too_many_unknown_bad_blocks(void **state) > +{ > + image.seek = 896; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_offs = 0; > + mtd_write_failure_1_errno = EIO; > + > + /* Initial flash state: | flash memory ends here > + * eb 56 57 58 59 60 61 62 63 | 64 > + * offset 896 912 928 944 960 976 992 1008 | 1024 > + * status bad ok bad ok bad bad bad ok->bad | */ > + { > + int indices[] = {56, 58, 60, 61, 62, -1}; > + set_multiple_bits(bad_blocks, indices); > + } > + mtd_write_failure_1_eb = 63; > + copy_flash_state(); > + > + { > + int indices[] = {57, 59, 63, -1}; > + clear_multiple_bits(expected_locked_blocks, indices); > + } > + memcpy(expected_flash_memory + 912, image_buf , 16); > + memcpy(expected_flash_memory + 944, image_buf + 16, 16); > + memset(expected_flash_memory + 1008, FLASH_EMPTY_BYTE, 16); > + set_bit(expected_bad_blocks, 63); > + clear_bit(expected_written_pages, 63 * 2); > + clear_bit(expected_written_pages, 63 * 2 + 1); > + > + run_flash_test(state, -ENOSPC); > +} > + > +static int mtd_is_bad_failure_1_errno; > +static int mtd_is_bad_failure_1(const struct mtd_dev_info *mtd, int fd, > int eb) > +{ > + check_args(mtd, fd, eb); > + errno = mtd_is_bad_failure_1_errno; > + return -1; > +} > + > +static void test_mtd_is_bad_not_supported(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_is_bad = mtd_is_bad_failure_1; > + mtd_is_bad_failure_1_errno = EOPNOTSUPP; > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_is_bad_failure(void **state) > +{ > + test_init(); > + impl_mtd_is_bad = mtd_is_bad_failure_1; > + mtd_is_bad_failure_1_errno = ERANGE; /* dummy value */ > + copy_flash_state(); > + run_flash_test(state, -mtd_is_bad_failure_1_errno); > +} > + > +static int mtd_is_locked_failure_1_errno; > +static int mtd_is_locked_failure_1(const struct mtd_dev_info *mtd, int fd, > + int eb) > +{ > + check_args(mtd, fd, eb); > + errno = mtd_is_locked_failure_1_errno; > + return -1; > +} > + > +static void test_mtd_is_locked_not_supported(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_is_locked = mtd_is_locked_failure_1; > + mtd_is_locked_failure_1_errno = EOPNOTSUPP; > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_is_locked_failure(void **state) > +{ > + test_init(); > + impl_mtd_is_locked = mtd_is_locked_failure_1; > + mtd_is_locked_failure_1_errno = ERANGE; /* dummy value */ > + copy_flash_state(); > + run_flash_test(state, -mtd_is_locked_failure_1_errno); > +} > + > +static int mtd_unlock_failure_1_errno; > +static int mtd_unlock_failure_1(const struct mtd_dev_info *mtd, int fd, > int eb) > +{ > + check_args(mtd, fd, eb); > + errno = mtd_unlock_failure_1_errno; > + return -1; > +} > + > +static void test_mtd_unlock_not_supported(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_unlock = mtd_unlock_failure_1; > + mtd_unlock_failure_1_errno = EOPNOTSUPP; > + memset(locked_blocks, 0, eb_bytes); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_unlock_failure(void **state) > +{ > + test_init(); > + impl_mtd_unlock = mtd_unlock_failure_1; > + mtd_unlock_failure_1_errno = ERANGE; /* dummy value */ > + copy_flash_state(); > + run_flash_test(state, -mtd_unlock_failure_1_errno); > +} > + > +static int mtd_read_failure_1_errno; > +static int mtd_read_failure_1(const struct mtd_dev_info *mtd, int fd, int > eb, > + int UNUSED offs, void UNUSED *buf, int UNUSED len) > +{ > + check_args(mtd, fd, eb); > + errno = mtd_read_failure_1_errno; > + return -1; > +} > + > +static void test_mtd_read_failure(void **state) > +{ > + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; > + test_init(); > + impl_mtd_read = mtd_read_failure_1; > + mtd_read_failure_1_errno = ERANGE; /* dummy value */ > + copy_flash_state(); > + clear_bit(expected_locked_blocks, 0); > + run_flash_test(state, -mtd_read_failure_1_errno); > +} > + > +static int mtd_erase_failure_1_eb; > +static int mtd_erase_failure_1_errno; > +static int mtd_erase_failure_1(libmtd_t desc, const struct mtd_dev_info > *mtd, > + int fd, int eb) > +{ > + if (eb == mtd_erase_failure_1_eb) { > + errno = mtd_erase_failure_1_errno; > + if (errno == EOPNOTSUPP) > + default_mtd_erase(desc, mtd, fd, eb); > + return -1; > + } > + return default_mtd_erase(desc, mtd, fd, eb); > +} > + > +static void test_mtd_read_no_erase_empty_flash_bytes(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; > + test_init(); > + memset(flash_memory, FLASH_EMPTY_BYTE, 16); > + impl_mtd_erase = mtd_erase_failure_1; > + mtd_erase_failure_1_eb = 0; > + mtd_erase_failure_1_errno = ERANGE; /* dummy value */ > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_erase_not_supported(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_erase = mtd_erase_failure_1; > + mtd_erase_failure_1_eb = 0; > + mtd_erase_failure_1_errno = EOPNOTSUPP; > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_erase_failure(void **state) > +{ > + test_init(); > + impl_mtd_erase = mtd_erase_failure_1; > + mtd_erase_failure_1_eb = 0; > + mtd_erase_failure_1_errno = ERANGE; /* dummy value */ > + copy_flash_state(); > + clear_bit(expected_locked_blocks, 0); > + run_flash_test(state, -mtd_erase_failure_1_errno); > +} > + > +static void test_mtd_erase_failure_EIO(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_erase = mtd_erase_failure_1; > + mtd_erase_failure_1_eb = 0; > + mtd_erase_failure_1_errno = EIO; > + > + copy_flash_state(); > + memcpy(expected_flash_memory + 16, image_buf, image.size); > + for (int i = 0; i <= 3; i++) > + clear_bit(expected_locked_blocks, i); > + set_bit(expected_bad_blocks, 0); > + > + run_flash_test(state, 0); > +} > + > +static void patch_image_buf_empty_bytes_2(unsigned char *buf) > +{ > + memset(buf, FLASH_EMPTY_BYTE, 16); > +} > + > +static void test_no_mtd_write_empty_image_bytes(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + patch_image_buf = patch_image_buf_empty_bytes_2; > + test_init(); > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 0; > + mtd_write_failure_1_offs = 0; > + mtd_write_failure_1_errno = ERANGE; /* dummy value */ > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + for (int i = 0; i <= 2; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 0); > + clear_bit(expected_written_pages, 1); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_write_failure(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = ERANGE; /* dummy value */ > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, 24); > + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); > + for (int i = 0; i <= 1; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 3); > + > + run_flash_test(state, -mtd_write_failure_1_errno); > +} > + > +static void test_mtd_write_bad_block(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = EIO; > + > + copy_flash_state(); > + set_bit(expected_bad_blocks, 1); > + clear_bit(expected_written_pages, 2); > + clear_bit(expected_written_pages, 3); > + memcpy(expected_flash_memory, image_buf, 16); > + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); > + memcpy(expected_flash_memory + 32, image_buf + 16, 32); > + for (int i = 0; i <= 3; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static int mtd_erase_failure_2_eb; > +static int mtd_erase_failure_2_idx; > +static int mtd_erase_failure_2_errno; > +static int mtd_erase_failure_2(libmtd_t desc, const struct mtd_dev_info > *mtd, > + int fd, int eb) > +{ > + check_args(mtd, fd, eb); > + if (eb == mtd_erase_failure_2_eb) { > + if (mtd_erase_failure_2_idx == 0) { > + errno = mtd_erase_failure_2_errno; > + return -1; > + } > + --mtd_erase_failure_2_idx; > + } > + return default_mtd_erase(desc, mtd, fd, eb); > +} > + > +static void test_mtd_write_bad_block_erase_failure(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = EIO; > + > + impl_mtd_erase = mtd_erase_failure_2; > + mtd_erase_failure_2_eb = 1; > + mtd_erase_failure_2_idx = 1; > + mtd_erase_failure_2_errno = ERANGE; /* dummy value */ > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, 24); > + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); > + for (int i = 0; i <= 1; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 3); > + > + run_flash_test(state, -mtd_erase_failure_2_errno); > +} > + > +static void test_mtd_write_bad_block_erase_failure_EIO(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = EIO; > + > + impl_mtd_erase = mtd_erase_failure_2; > + mtd_erase_failure_2_eb = 1; > + mtd_erase_failure_2_idx = 1; > + mtd_erase_failure_2_errno = EIO; > + > + copy_flash_state(); > + set_bit(expected_bad_blocks, 1); > + clear_bit(expected_written_pages, 3); > + memcpy(expected_flash_memory, image_buf, 24); > + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); > + memcpy(expected_flash_memory + 32, image_buf + 16, 32); > + for (int i = 0; i <= 3; i++) > + clear_bit(expected_locked_blocks, i); > + > + run_flash_test(state, 0); > +} > + > +static int mtd_mark_bad_failure_1_errno; > +static int mtd_mark_bad_failure_1(const struct mtd_dev_info *mtd, int fd, > + int eb) > +{ > + check_args(mtd, fd, eb); > + errno = mtd_mark_bad_failure_1_errno; > + return -1; > +} > + > +static void test_mtd_write_bad_block_mark_not_supported(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = EIO; > + > + impl_mtd_mark_bad = mtd_mark_bad_failure_1; > + mtd_mark_bad_failure_1_errno = EOPNOTSUPP; > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, 16); > + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); > + memcpy(expected_flash_memory + 32, image_buf + 16, 32); > + > + for (int i = 0; i <= 3; i++) > + clear_bit(expected_locked_blocks, i); > + for (int i = 2; i <= 3; i++) > + clear_bit(expected_written_pages, i); > + > + run_flash_test(state, 0); > +} > + > +static void test_mtd_write_bad_block_mark_failure(void **state) > +{ > + image.seek = 0; > + image.size = 48; > + mtd_ubi_info_s.mtd.size = 1024; > + mtd_ubi_info_s.mtd.eb_size = 16; > + mtd_ubi_info_s.mtd.min_io_size = 8; > + test_init(); > + > + impl_mtd_write = mtd_write_failure_1; > + mtd_write_failure_1_eb = 1; > + mtd_write_failure_1_offs = 8; > + mtd_write_failure_1_errno = EIO; > + > + impl_mtd_mark_bad = mtd_mark_bad_failure_1; > + mtd_mark_bad_failure_1_errno = ERANGE; /* dummy value */ > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, 16); > + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); > + for (int i = 0; i <= 1; i++) > + clear_bit(expected_locked_blocks, i); > + for (int i = 2; i <= 3; i++) > + clear_bit(expected_written_pages, i); > + > + run_flash_test(state, -mtd_mark_bad_failure_1_errno); > +} > + > +static void test_multiple_callbacks(void **state) > +{ > + /* flash_write() is typically executed with (len <= 16 * 1024) */ > + image.seek = 0; > + image.size = 63 * 1024; > + mtd_ubi_info_s.mtd.size = 64 * 1024; > + mtd_ubi_info_s.mtd.eb_size = 8 * 1024; > + mtd_ubi_info_s.mtd.min_io_size = 1024; > + test_init(); > + > + copy_flash_state(); > + memcpy(expected_flash_memory, image_buf, image.size); > + memset(expected_flash_memory + 63 * 1024, FLASH_EMPTY_BYTE, 1024); > + for (int i = 0; i <= 7; i++) > + clear_bit(expected_locked_blocks, i); > + clear_bit(expected_written_pages, 63); > + > + run_flash_test(state, 0); > +} > + > +#define TEST(name) \ > + cmocka_unit_test_setup_teardown(test_##name, test_setup, test_teardown) > + > +int main(void) > +{ > + static const struct CMUnitTest tests[] = { > + TEST(simple), > + TEST(simple_NOR), > + TEST(padding_less_than_page), > + TEST(padding_page), > + TEST(skip_write_page_empty_bytes), > + TEST(padding_more_than_page), > + TEST(seek), > + TEST(seek_not_multiple_of_eb_size), > + TEST(not_enough_flash), > + TEST(invalid_mtd_device), > + TEST(invalid_image_size), > + TEST(empty_image), > + TEST(mtd_dev_open_failure), > + TEST(malloc_failure), > + TEST(skip_bad_blocks), > + TEST(too_many_known_bad_blocks), > + TEST(too_many_unknown_bad_blocks), > + TEST(mtd_is_bad_not_supported), > + TEST(mtd_is_bad_failure), > + TEST(mtd_is_locked_not_supported), > + TEST(mtd_is_locked_failure), > + TEST(mtd_unlock_not_supported), > + TEST(mtd_unlock_failure), > + TEST(mtd_read_failure), > + TEST(mtd_read_no_erase_empty_flash_bytes), > + TEST(mtd_erase_not_supported), > + TEST(mtd_erase_failure), > + TEST(mtd_erase_failure_EIO), > + TEST(no_mtd_write_empty_image_bytes), > + TEST(mtd_write_failure), > + TEST(mtd_write_bad_block), > + TEST(mtd_write_bad_block_erase_failure), > + TEST(mtd_write_bad_block_erase_failure_EIO), > + TEST(mtd_write_bad_block_mark_not_supported), > + TEST(mtd_write_bad_block_mark_failure), > + TEST(multiple_callbacks), > + }; > + return cmocka_run_group_tests_name("flash_handler", tests, group_setup, > + group_teardown); > +} > -- > 2.45.2 > >
Hi Rob, thanks for your feedback. On Tuesday, January 14, 2025 at 10:49:21 PM UTC+4 Rob Lee wrote: > Regarding treatment of -EIO upon erase or write operations, let's treat MTD UBI code as the "industry standard". Here is the Linux upstream UBI implementation: > https://github.com/torvalds/linux/tree/master/drivers/mtd/ubi Currently my patch is based on original flash handler implementation which in turn was probably based on nandwrite from mtd-utils: https://git.infradead.org/?p=mtd-utils.git;a=blob;f=nand-utils/nandwrite.c I looked into UBI layer implementation in kernel and noticed the following differences comparing to my implementation: 1) UBI layer reads data back after successfull mtd_write() and does memcmp() in order to confirm that the data has been written correctly. 2) UBI layer in case of mtd_erase() failure (regardless of the specific error code) retries mtd_erase() up to UBI_IO_RETRIES=3 times (yielding CPU in-between retries). 3) UBI layer reads data back after successful mtd_erase() to make sure the memory is filled with 0xFF. 4) UBI layer reschedules mtd_erase() in case it failed with one of the following errors: EINTR, ENOMEM, EAGAIN, EBUSY. 5) UBI layer doesn't mark a block bad in case mtd_write() returns EIO (this idea seem to be originated from nandwrite/mtd-utils implementation). I'm not sure whether we should adopt practices #1, #2, #3 in swupdate as well (look somewhat excessive to me). > In this code, there are cases where an -EIO upon erase (maybe write also, can't remember) does not automatically mark the block as bad but instead allow the block to be "tortured" to see if the EIO error was some odd temporary issue (bus issue? NAND memory temporary condition?) or permanent. In a device I was involved with in the past, a particular NAND vendor had NAND memory problems due to the manufacturing process errors that cause "false" temporary EIO errors to be generated, but the block was not permanently bad. So the UBI torture test capability was useful for to save these devices (millions of devices) from failure due running out of spare blocks from marking blocks bad on the first EIO error return. I understood a general idea, but specific proposed implementation of the idea is not yet clear to me. As far as I understood __erase_worker() in UBI layer, they do mark a block as bad if after 3 failures mtd_erase() still returns EIO (please correct me if I'm wrong here). See: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/mtd/ubi/wl.c?h=v6.13#n1198 I'm also not sure how/when "torturing" comes into play here. > Given that explanation, can you provide the caller with an option to pass it the MTD error and let the caller retry and mark bad as they see fit? I'm not sure I fully understand the proposal here.. We could add a new swupdate command line option (let's name it "--no-mark-bad") so that when user passes this option, swupdate just fails on first EIO (reports problematic eraseblock but doesn't mark it as bad). IMHO by default we should mark blocks bad as we go (to stay compatible with our previous implementation). Is this what you mean? Off-topic: I kindly ask you to explicitly add my email (additionally to swupdate@googlegroups.com) to your replies (currently emails not addressing me directly don't appear in my inbox). Regards, Viacheslav
diff --git a/.gitignore b/.gitignore index 4755ff07..313a8b55 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ tools/*.out test/test_crypt test/test_hash test/test_json +test/test_flash_handler test/test_server_hawkbit test/test_util test/test_verify diff --git a/configs/test_defconfig b/configs/test_defconfig index ac8fff6d..5fef117b 100644 --- a/configs/test_defconfig +++ b/configs/test_defconfig @@ -14,3 +14,5 @@ CONFIG_LUASCRIPTHANDLER=y CONFIG_RAW=y CONFIG_REMOTE_HANDLER=y CONFIG_SHELLSCRIPTHANDLER=y +CONFIG_MTD=y +CONFIG_CFI=y diff --git a/test/Makefile b/test/Makefile index ad26b40d..6f3da093 100644 --- a/test/Makefile +++ b/test/Makefile @@ -27,6 +27,7 @@ endif tests-$(CONFIG_SURICATTA_HAWKBIT) += test_json tests-$(CONFIG_SURICATTA_HAWKBIT) += test_server_hawkbit tests-y += test_util +tests-$(CONFIG_CFI) += test_flash_handler ccflags-y += -I$(src)/../ diff --git a/test/test_flash_handler.c b/test/test_flash_handler.c new file mode 100644 index 00000000..56264558 --- /dev/null +++ b/test/test_flash_handler.c @@ -0,0 +1,1456 @@ +/* + * Author: Viacheslav Volkov + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stddef.h> +#include <stdarg.h> +#include <setjmp.h> +#include <cmocka.h> + +#include <stdint.h> +#include <libmtd.h> +#include <mtd/mtd-user.h> + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include "util.h" +#include "pctl.h" +#include "flash.h" +#include "handler.h" +#include "swupdate_image.h" +#include "swupdate_status.h" + +/* #define PRINT_LOGS */ +#define RETURN_CODE_ENOSPC +#define DEV_MTD_PREFIX "/dev/mtd" +#define MTD_DEV_IDX 0 +/* MTD_FD is a big dummy value: it is very unlikely for us to have so many open + * file descriptors: */ +#define MTD_FD 999999 +#define LIBMTD_T_VALUE ((libmtd_t)0x12345678) /* dummy value */ + +#define FOREACH_PAGE_IN_EB(page, mtd, eb) \ + for (page = (eb) * ((mtd)->eb_size / (mtd)->min_io_size); \ + page < (eb + 1) * ((mtd)->eb_size / (mtd)->min_io_size); \ + page++) + +#define FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) \ + for (page = ((eb) * (mtd)->eb_size + (offs)) / (mtd)->min_io_size; \ + page < ((eb) * (mtd)->eb_size + (offs) + (len)) / (mtd)->min_io_size; \ + page++) + +/* + * Mock data + */ +static struct img_type image = { + .type = "flash", +}; +static struct mtd_ubi_info mtd_ubi_info_s; + +/* + * Test-only data + */ +static handler handler_func; +static bool is_nand; +static unsigned char *image_buf; +static void (*patch_image_buf)(unsigned char *buf); +static int eb_bytes; +static int pages_bytes; +/* Actual flash state before test run: */ +static unsigned char *bad_blocks; +static unsigned char *locked_blocks; +static unsigned char *written_pages; +static unsigned char *flash_memory; +/* Expected flash state after test run: */ +static unsigned char *expected_bad_blocks; +static unsigned char *expected_locked_blocks; +static unsigned char *expected_written_pages; +static unsigned char *expected_flash_memory; + +void *__real_malloc(size_t size); +static void *(*impl_malloc)(size_t size) = __real_malloc; +void *__wrap_malloc(size_t size); +void *__wrap_malloc(size_t size) +{ + return impl_malloc(size); +} + +int __real_open(const char *pathname, int flags); +static int default_open(const char *pathname, int flags) +{ + int ret; + if (strcmp(pathname, image.device) == 0) + return MTD_FD; + ret = __real_open(pathname, flags); + /* Usually file descriptors start from 0. Since MTD_FD is big enough, + * a collision is very unlikely. */ + assert_int_not_equal(ret, MTD_FD); + return ret; +} +static int (*impl_open)(const char *pathname, int flags) = default_open; +int __wrap_open(const char *pathname, int flags); +int __wrap_open(const char *pathname, int flags) +{ + return impl_open(pathname, flags); +} + +int __real_close(int fd); +int __wrap_close(int fd); +int __wrap_close(int fd) +{ + if (fd == MTD_FD) + return 0; + return __real_close(fd); +} + +off_t __real_lseek(int fd, off_t offset, int whence); +off_t __wrap_lseek(int fd, off_t offset, int whence); +off_t __wrap_lseek(int fd, off_t offset, int whence) +{ + if (fd == MTD_FD) { + /* __swupdate_copy() executes lseek() on /dev/mtdX. */ + assert_int_equal(image.seek, offset); + assert_int_equal(SEEK_SET, whence); + return offset; + } + return __real_lseek(fd, offset, whence); +} + +int __wrap_mtd_dev_present(libmtd_t desc, int mtd_num); +int __wrap_mtd_dev_present(libmtd_t UNUSED desc, int mtd_num) +{ + assert_int_equal(MTD_DEV_IDX, mtd_num); + return 1; +} + +int __wrap_get_mtd_from_device(char *s); +int __wrap_get_mtd_from_device(char *s) +{ + int ret, mtdnum; + assert_ptr_not_equal(NULL, s); + if (strlen(s) < sizeof(DEV_MTD_PREFIX)) + return -1; + ret = sscanf(s, DEV_MTD_PREFIX "%d", &mtdnum); + if (ret <= 0) + return -1; + return mtdnum; +} + +static unsigned char get_byte_idx(int bit_idx) +{ + return bit_idx / CHAR_BIT; +} + +static unsigned char get_mask(int bit_idx) +{ + return 1 << (bit_idx % CHAR_BIT); +} + +static bool get_bit(unsigned char *data, int bit_idx) +{ + return (data[get_byte_idx(bit_idx)] & get_mask(bit_idx)) != 0; +} + +static void set_bit(unsigned char *data, int bit_idx) +{ + data[get_byte_idx(bit_idx)] |= get_mask(bit_idx); +} + +static void clear_bit(unsigned char *data, int bit_idx) +{ + data[get_byte_idx(bit_idx)] &= (~get_mask(bit_idx)); +} + +static void set_multiple_bits(unsigned char *data, const int *bit_indices) +{ + int bit_idx = bit_indices[0]; + for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i]) + set_bit(data, bit_idx); +} + +static void clear_multiple_bits(unsigned char *data, const int *bit_indices) +{ + int bit_idx = bit_indices[0]; + for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i]) + clear_bit(data, bit_idx); +} + +static void check_args(const struct mtd_dev_info *mtd, int fd, int eb) +{ + assert_ptr_equal(&mtd_ubi_info_s.mtd, mtd); + assert_true( + (mtd->type == MTD_NORFLASH) || + (mtd->type == MTD_NANDFLASH)); + assert_int_equal(MTD_FD, fd); + assert_true(eb >= 0); + assert_true(eb < mtd->size / mtd->eb_size); +} + +static int default_mtd_get_bool(const struct mtd_dev_info UNUSED *mtd, + int UNUSED fd, int eb, unsigned char *data) +{ + return get_bit(data, eb) ? 1 : 0; +} + +static int default_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb) +{ + return default_mtd_get_bool(mtd, fd, eb, bad_blocks); +} +static int (*impl_mtd_is_bad)(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + return impl_mtd_is_bad(mtd, fd, eb); +} + +static int default_mtd_mark_bad(const struct mtd_dev_info UNUSED *mtd, + int UNUSED fd, int eb) +{ + set_bit(bad_blocks, eb); + return 0; +} +static int (*impl_mtd_mark_bad)(const struct mtd_dev_info *mtd, int fd, + int eb); +int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + assert_false(get_bit(bad_blocks, eb)); + /* Note: we don't require erasing a block before marking it bad. */ + return impl_mtd_mark_bad(mtd, fd, eb); +} + +static int default_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, + int eb) +{ + return default_mtd_get_bool(mtd, fd, eb, locked_blocks); +} +static int (*impl_mtd_is_locked)(const struct mtd_dev_info *mtd, int fd, + int eb); +int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + return impl_mtd_is_locked(mtd, fd, eb); +} + +static int default_mtd_unlock(const struct mtd_dev_info UNUSED *mtd, + int UNUSED fd, int eb) +{ + clear_bit(locked_blocks, eb); + return 0; +} +static int (*impl_mtd_unlock)(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb); +int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + assert_false(get_bit(bad_blocks, eb)); + /* Unlocking already unlocked blocks is totaly fine: + * - not every flash supports mtd_is_locked() check; + * - some implementation might prefer to mtd_unlock() rigth away + * without checking mtd_is_locked(). */ + return impl_mtd_unlock(mtd, fd, eb); +} + +static int default_mtd_erase(libmtd_t UNUSED desc, + const struct mtd_dev_info *mtd, int UNUSED fd, int eb) +{ + unsigned int page; + memset(flash_memory + eb * mtd->eb_size, FLASH_EMPTY_BYTE, + mtd->eb_size); + FOREACH_PAGE_IN_EB(page, mtd, eb) + clear_bit(written_pages, page); + return 0; +} +static int (*impl_mtd_erase)(libmtd_t desc, const struct mtd_dev_info *mtd, + int fd, int eb); +int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int fd, + int eb); +int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int fd, + int eb) +{ + check_args(mtd, fd, eb); + assert_false(get_bit(bad_blocks, eb)); + assert_false(get_bit(locked_blocks, eb)); + return impl_mtd_erase(desc, mtd, fd, eb); +} + +static int default_mtd_write(libmtd_t UNUSED desc, + const struct mtd_dev_info *mtd, int UNUSED fd, int eb, + int offs, void *data, int len, void UNUSED *oob, + int UNUSED ooblen, uint8_t UNUSED mode) +{ + unsigned int page; + unsigned int flash_offset = eb * mtd->eb_size + offs; + const unsigned char *pdata = (const unsigned char*)data; + FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) { + set_bit(written_pages, page); + memcpy(flash_memory + flash_offset, pdata, mtd->min_io_size); + flash_offset += mtd->min_io_size; + pdata += mtd->min_io_size; + } + return 0; +} + +static int (*impl_mtd_write)(libmtd_t desc, const struct mtd_dev_info *mtd, + int fd, int eb, int offs, void *data, int len, void *oob, + int ooblen, uint8_t mode); +int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int fd, + int eb, int offs, void *data, int len, void *oob, int ooblen, + uint8_t mode); + +int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int fd, + int eb, int offs, void *data, int len, void *oob, int ooblen, + uint8_t mode) +{ + unsigned int page; + check_args(mtd, fd, eb); + assert_true(offs >= 0); + assert_ptr_not_equal(NULL, data); + assert_true(len > 0); + assert_true(offs + len <= mtd->eb_size); + assert_ptr_equal(NULL, oob); + assert_int_equal(0, ooblen); + assert_false(get_bit(bad_blocks, eb)); + assert_false(get_bit(locked_blocks, eb)); + assert_true(offs <= mtd->eb_size - mtd->min_io_size); + assert_int_equal(0, offs % mtd->min_io_size); + assert_int_equal(0, len % mtd->min_io_size); + + if (mtd->type == MTD_NANDFLASH) { + /* Follow "write once rule" for NAND flash. */ + FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) + assert_false(get_bit(written_pages, page)); + } else { + /* Assume it is ok to write multiple times to NOR flash. */ + unsigned int flash_i = eb * mtd->eb_size + offs; + for (int data_i = 0; data_i < len; data_i++) { + uint8_t old_byte = flash_memory[flash_i + data_i]; + uint8_t new_byte = ((uint8_t*)data)[data_i]; + assert_true((old_byte & new_byte) == new_byte); + } + } + return impl_mtd_write(desc, mtd, fd, eb, offs, data, len, oob, ooblen, + mode); +} + +static int default_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, + int offs, void *buf, int len) +{ + unsigned int flash_offset = eb * mtd->eb_size + offs; + check_args(mtd, fd, eb); + memcpy(buf, flash_memory + flash_offset, len); + return 0; +} +static int (*impl_mtd_read)(const struct mtd_dev_info *mtd, int fd, int eb, + int offs, void *buf, int len); +int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs, + void *buf, int len); +int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs, + void *buf, int len) +{ + check_args(mtd, fd, eb); + assert_true(offs >= 0); + assert_ptr_not_equal(NULL, buf); + assert_true(len > 0); + assert_true(offs + len <= mtd->eb_size); + assert_false(get_bit(bad_blocks, eb)); + assert_false(get_bit(locked_blocks, eb)); + assert_true(offs <= mtd->eb_size - mtd->min_io_size); + assert_int_equal(0, offs % mtd->min_io_size); + assert_int_equal(0, len % mtd->min_io_size); + return impl_mtd_read(mtd, fd, eb, offs, buf, len); +} + +static unsigned char *generate_image_file(const char *name, size_t size) +{ + int r, fd; + unsigned char *buf = malloc(size); + unsigned char *pbuf = buf; + assert_ptr_not_equal(NULL, buf); + fd = open(name, O_WRONLY); + if (fd < 0) + free(buf); + assert_true(fd >= 0); + + for (size_t i = 0; i < size; i++) + buf[i] = (unsigned char)i; + + if (patch_image_buf) + patch_image_buf(buf); + + while (size > 0) { + ssize_t ret = write(fd, pbuf, size); + if (ret < 0) { + close(fd); + free(buf); + } + assert_true(ret >= 0); + pbuf += ret; + size -= ret; + } + + r = close(fd); + assert_int_equal(0, r); + return buf; +} + +static int group_setup(void UNUSED **state) +{ + int ret, fd; + struct flash_description *flash = get_flash_info(); + struct installer_handler *hnd = find_handler(&image); + + assert_ptr_not_equal(NULL, hnd); + handler_func = hnd->installer; + assert_ptr_not_equal(NULL, handler_func); + flash->mtd_info = &mtd_ubi_info_s; + flash->libmtd = LIBMTD_T_VALUE; + +#ifdef PRINT_LOGS + loglevel = DEBUGLEVEL; + notify_init(); +#endif + + strcpy(image.fname, "swupdate_image_XXXXXX.bin"); + fd = mkstemps(image.fname, 4 /* = strlen(".bin") */); + if (fd < 0) + image.fname[0] = 0; /* required for group_teardown() */ + ret = close(fd); + assert_int_equal(0, ret); + return 0; +} + +static int group_teardown(void UNUSED **state) +{ + if (image.fname[0] != 0) { + int ret = unlink(image.fname); + assert_int_equal(0, ret); + } + return 0; +} + +static void copy_flash_state(void) +{ + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; + + expected_bad_blocks = malloc(eb_bytes); + assert_ptr_not_equal(NULL, expected_bad_blocks); + memcpy(expected_bad_blocks, bad_blocks, eb_bytes); + + expected_locked_blocks = malloc(eb_bytes); + assert_ptr_not_equal(NULL, expected_locked_blocks); + memcpy(expected_locked_blocks, locked_blocks, eb_bytes); + + expected_written_pages = malloc(pages_bytes); + assert_ptr_not_equal(NULL, expected_written_pages); + memcpy(expected_written_pages, written_pages, pages_bytes); + + expected_flash_memory = malloc(mtd->size); + assert_ptr_not_equal(NULL, expected_flash_memory); + memcpy(expected_flash_memory, flash_memory, mtd->size); +} + +static void init_flash_state(void) +{ + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; + int num = (int)(mtd->size / mtd->eb_size); + eb_bytes = num / CHAR_BIT; + if (num % CHAR_BIT) + eb_bytes++; + /* By default there are no any bad blocks: */ + bad_blocks = calloc(eb_bytes, 1); + assert_ptr_not_equal(NULL, bad_blocks); + /* By default all blocks are locked: */ + locked_blocks = malloc(eb_bytes); + assert_ptr_not_equal(NULL, locked_blocks); + memset(locked_blocks, 0xFF, eb_bytes); + + num = (int)(mtd->size / mtd->min_io_size); + pages_bytes = num / CHAR_BIT; + if (num % CHAR_BIT) + pages_bytes++; + /* By default all pages are written (require erase): */ + written_pages = malloc(pages_bytes); + assert_ptr_not_equal(NULL, written_pages); + memset(written_pages, 0xFF, pages_bytes); + + flash_memory = malloc(mtd->size); + assert_ptr_not_equal(NULL, flash_memory); + /* Fill flash memory with initial pattern: */ + memset(flash_memory, 0xA5, mtd->size); +} + +static void test_init(void) +{ + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; + + /* Unit test config (precondition) sanity check: */ + assert_true(mtd->size >= mtd->eb_size); + assert_true(mtd->eb_size >= mtd->min_io_size); + assert_int_equal(0, mtd->size % mtd->eb_size); + assert_int_equal(0, mtd->eb_size % mtd->min_io_size); + + is_nand = (mtd->type == MTD_NANDFLASH) || + (mtd->type == MTD_MLCNANDFLASH); + image_buf = generate_image_file(image.fname, image.size); + image.fdin = open(image.fname, O_RDONLY); + assert_true(image.fdin >= 0); + + init_flash_state(); +} + +static void verify_flash_state(void) +{ + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; + assert_memory_equal(expected_bad_blocks, bad_blocks, eb_bytes); + assert_memory_equal(expected_locked_blocks, locked_blocks, eb_bytes); + assert_memory_equal(expected_written_pages, written_pages, + pages_bytes); + assert_memory_equal(expected_flash_memory, flash_memory, mtd->size); +} + +static void run_flash_test(void UNUSED **state, int expected_return_code) +{ + int ret = handler_func(&image, NULL); +#ifdef RETURN_CODE_ENOSPC + /* Currently on callback() failure __swupdate_copy() always returns + * -ENOSPC (no matter what callback() returned). Hence some subset of + * flash handler return values are converted to -ENOSPC. Here we'll + * convert rest of return values to -ENOSPC to map expected and actual + * return values to a simple success/failure check. */ + if (ret < 0) + ret = -ENOSPC; + if (expected_return_code < 0) + expected_return_code = -ENOSPC; +#endif + assert_int_equal(expected_return_code, ret); + verify_flash_state(); +} + +static int test_setup(void UNUSED **state) +{ + struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd; + image_buf = NULL; + patch_image_buf = NULL; + image.fdin = -1; + + bad_blocks = NULL; + locked_blocks = NULL; + written_pages = NULL; + flash_memory = NULL; + + expected_bad_blocks = NULL; + expected_locked_blocks = NULL; + expected_written_pages = NULL; + expected_flash_memory = NULL; + + /* Tests can overwrite the following default implementations to inject + * errors: */ + impl_malloc = __real_malloc; + impl_open = default_open; + impl_mtd_is_bad = default_mtd_is_bad; + impl_mtd_mark_bad = default_mtd_mark_bad; + impl_mtd_is_locked = default_mtd_is_locked; + impl_mtd_unlock = default_mtd_unlock; + impl_mtd_erase = default_mtd_erase; + impl_mtd_write = default_mtd_write; + impl_mtd_read = default_mtd_read; + + /* Some default values: */ + mtd->type = MTD_NANDFLASH; + strcpy(image.device, DEV_MTD_PREFIX STR(MTD_DEV_IDX)); + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + return 0; +} + +static int test_teardown(void UNUSED **state) +{ + unsigned char *pointer[] = { + bad_blocks, + locked_blocks, + written_pages, + flash_memory, + + expected_bad_blocks, + expected_locked_blocks, + expected_written_pages, + expected_flash_memory, + + image_buf, + }; + impl_malloc = __real_malloc; + for (int i = 0; i < ARRAY_SIZE(pointer); i++) + free(pointer[i]); + if (image.fdin >= 0) { + int ret = close(image.fdin); + assert_int_equal(0, ret); + } + return 0; +} + +static void test_simple(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_simple_NOR(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 1; + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_padding_less_than_page(void **state) +{ + image.seek = 0; + image.size = 42; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + memset(expected_flash_memory + 42, FLASH_EMPTY_BYTE, 6); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_padding_page(void **state) +{ + image.seek = 0; + image.size = 40; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + memset(expected_flash_memory + 40, FLASH_EMPTY_BYTE, 8); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 5); + + run_flash_test(state, 0); +} + +static void patch_image_buf_empty_bytes_1(unsigned char *buf) +{ + memset(buf + 8, FLASH_EMPTY_BYTE, 24); +} + +static void test_skip_write_page_empty_bytes(void **state) +{ + image.seek = 0; + image.size = 40; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + patch_image_buf = patch_image_buf_empty_bytes_1; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + memset(expected_flash_memory + 40, FLASH_EMPTY_BYTE, 8); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + if (is_nand) + clear_bit(expected_written_pages, 1); + clear_bit(expected_written_pages, 2); + clear_bit(expected_written_pages, 3); + clear_bit(expected_written_pages, 5); + + run_flash_test(state, 0); +} + +static void test_padding_more_than_page(void **state) +{ + image.seek = 0; + image.size = 37; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + memset(expected_flash_memory + 37, FLASH_EMPTY_BYTE, 11); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 5); + + run_flash_test(state, 0); +} + +static void test_seek(void **state) +{ + image.seek = 16; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory + 16, image_buf, image.size); + for (int i = 1; i <= 3; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_seek_not_multiple_of_eb_size(void **state) +{ + image.seek = 8; + mtd_ubi_info_s.mtd.eb_size = 16; + test_init(); + copy_flash_state(); + run_flash_test(state, -EINVAL); +} + +static void test_not_enough_flash(void **state) +{ + image.seek = 16; + image.size = 1020; + mtd_ubi_info_s.mtd.size = 1024; + test_init(); + copy_flash_state(); + run_flash_test(state, -ENOSPC); +} + +static void test_invalid_mtd_device(void **state) +{ + test_init(); + strcpy(image.device, "/dev/mtdX"); + copy_flash_state(); + run_flash_test(state, -EINVAL); +} + +static void test_invalid_image_size(void **state) +{ + test_init(); + image.size = -42; + copy_flash_state(); + run_flash_test(state, -42); +} + +static void test_empty_image(void **state) +{ + test_init(); + image.size = 0; + copy_flash_state(); + run_flash_test(state, 0); +} + +static int open_mtd_dev_failure_errno; +static int open_mtd_dev_failure(const char *pathname, int flags) +{ + if (strcmp(pathname, image.device) == 0) { + errno = open_mtd_dev_failure_errno; + return -1; + } + return default_open(pathname, flags); +} +static void test_mtd_dev_open_failure(void **state) +{ + test_init(); + impl_open = open_mtd_dev_failure; + open_mtd_dev_failure_errno = EPERM; + copy_flash_state(); + run_flash_test(state, -open_mtd_dev_failure_errno); +} + +static int malloc_filebuf_allocation_failure_size; +static void *malloc_filebuf_allocation_failure(size_t size) +{ + if (size == malloc_filebuf_allocation_failure_size) { + errno = ENOMEM; + return NULL; + } + return __real_malloc(size); +} +static void test_malloc_failure(void **state) +{ + test_init(); + copy_flash_state(); + impl_malloc = malloc_filebuf_allocation_failure; + malloc_filebuf_allocation_failure_size = mtd_ubi_info_s.mtd.eb_size; + if (!is_nand) { + /* filebuf allocation includes space for readout buffer */ + malloc_filebuf_allocation_failure_size *= 2; + } + run_flash_test(state, -ENOMEM); +} + +static void test_skip_bad_blocks(void **state) +{ + image.seek = 896; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + /* Initial flash state: | flash memory ends here + * eb 56 57 58 59 60 61 62 63 | 64 + * offset 896 912 928 944 960 976 992 1008 | 1024 + * status bad ok bad ok bad bad ok bad | */ + + { + int indices[] = {56, 58, 60, 61, 63, -1}; + set_multiple_bits(bad_blocks, indices); + } + copy_flash_state(); + + { + int indices[] = {57, 59, 62, -1}; + clear_multiple_bits(expected_locked_blocks, indices); + } + memcpy(expected_flash_memory + 912, image_buf , 16); + memcpy(expected_flash_memory + 944, image_buf + 16, 16); + memcpy(expected_flash_memory + 992, image_buf + 32, 16); + + run_flash_test(state, 0); +} + +static void test_too_many_known_bad_blocks(void **state) +{ + image.seek = 896; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + /* Initial flash state: | flash memory ends here + * eb 56 57 58 59 60 61 62 63 | 64 + * offset 896 912 928 944 960 976 992 1008 | 1024 + * status bad ok bad ok bad bad bad bad | */ + + { + int indices[] = {56, 58, 60, 61, 62, 63, -1}; + set_multiple_bits(bad_blocks, indices); + } + copy_flash_state(); + + { + int indices[] = {57, 59, -1}; + clear_multiple_bits(expected_locked_blocks, indices); + } + memcpy(expected_flash_memory + 912, image_buf , 16); + memcpy(expected_flash_memory + 944, image_buf + 16, 16); + + run_flash_test(state, -ENOSPC); +} + +static int mtd_write_failure_1_eb; +static int mtd_write_failure_1_offs; +static int mtd_write_failure_1_errno; +static int mtd_write_failure_1(libmtd_t desc, const struct mtd_dev_info *mtd, + int fd, int eb, int offs, void *data, int len, void *oob, + int ooblen, uint8_t mode) +{ + if (eb == mtd_write_failure_1_eb) { + unsigned char *p_data = (unsigned char *)data; + for (int o = offs; o < offs + len; o += mtd->min_io_size) { + int ret; + if (o == mtd_write_failure_1_offs) { + errno = mtd_write_failure_1_errno; + return -1; + } + ret = default_mtd_write(desc, mtd, fd, eb, o, + p_data, mtd->min_io_size, oob, + ooblen, mode); + assert_int_equal(0, ret); + p_data += mtd->min_io_size; + } + } + return default_mtd_write(desc, mtd, fd, eb, offs, data, len, oob, + ooblen, mode); +} + +static void test_too_many_unknown_bad_blocks(void **state) +{ + image.seek = 896; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_offs = 0; + mtd_write_failure_1_errno = EIO; + + /* Initial flash state: | flash memory ends here + * eb 56 57 58 59 60 61 62 63 | 64 + * offset 896 912 928 944 960 976 992 1008 | 1024 + * status bad ok bad ok bad bad bad ok->bad | */ + { + int indices[] = {56, 58, 60, 61, 62, -1}; + set_multiple_bits(bad_blocks, indices); + } + mtd_write_failure_1_eb = 63; + copy_flash_state(); + + { + int indices[] = {57, 59, 63, -1}; + clear_multiple_bits(expected_locked_blocks, indices); + } + memcpy(expected_flash_memory + 912, image_buf , 16); + memcpy(expected_flash_memory + 944, image_buf + 16, 16); + memset(expected_flash_memory + 1008, FLASH_EMPTY_BYTE, 16); + set_bit(expected_bad_blocks, 63); + clear_bit(expected_written_pages, 63 * 2); + clear_bit(expected_written_pages, 63 * 2 + 1); + + run_flash_test(state, -ENOSPC); +} + +static int mtd_is_bad_failure_1_errno; +static int mtd_is_bad_failure_1(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + errno = mtd_is_bad_failure_1_errno; + return -1; +} + +static void test_mtd_is_bad_not_supported(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_is_bad = mtd_is_bad_failure_1; + mtd_is_bad_failure_1_errno = EOPNOTSUPP; + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_mtd_is_bad_failure(void **state) +{ + test_init(); + impl_mtd_is_bad = mtd_is_bad_failure_1; + mtd_is_bad_failure_1_errno = ERANGE; /* dummy value */ + copy_flash_state(); + run_flash_test(state, -mtd_is_bad_failure_1_errno); +} + +static int mtd_is_locked_failure_1_errno; +static int mtd_is_locked_failure_1(const struct mtd_dev_info *mtd, int fd, + int eb) +{ + check_args(mtd, fd, eb); + errno = mtd_is_locked_failure_1_errno; + return -1; +} + +static void test_mtd_is_locked_not_supported(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_is_locked = mtd_is_locked_failure_1; + mtd_is_locked_failure_1_errno = EOPNOTSUPP; + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_mtd_is_locked_failure(void **state) +{ + test_init(); + impl_mtd_is_locked = mtd_is_locked_failure_1; + mtd_is_locked_failure_1_errno = ERANGE; /* dummy value */ + copy_flash_state(); + run_flash_test(state, -mtd_is_locked_failure_1_errno); +} + +static int mtd_unlock_failure_1_errno; +static int mtd_unlock_failure_1(const struct mtd_dev_info *mtd, int fd, int eb) +{ + check_args(mtd, fd, eb); + errno = mtd_unlock_failure_1_errno; + return -1; +} + +static void test_mtd_unlock_not_supported(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_unlock = mtd_unlock_failure_1; + mtd_unlock_failure_1_errno = EOPNOTSUPP; + memset(locked_blocks, 0, eb_bytes); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_mtd_unlock_failure(void **state) +{ + test_init(); + impl_mtd_unlock = mtd_unlock_failure_1; + mtd_unlock_failure_1_errno = ERANGE; /* dummy value */ + copy_flash_state(); + run_flash_test(state, -mtd_unlock_failure_1_errno); +} + +static int mtd_read_failure_1_errno; +static int mtd_read_failure_1(const struct mtd_dev_info *mtd, int fd, int eb, + int UNUSED offs, void UNUSED *buf, int UNUSED len) +{ + check_args(mtd, fd, eb); + errno = mtd_read_failure_1_errno; + return -1; +} + +static void test_mtd_read_failure(void **state) +{ + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; + test_init(); + impl_mtd_read = mtd_read_failure_1; + mtd_read_failure_1_errno = ERANGE; /* dummy value */ + copy_flash_state(); + clear_bit(expected_locked_blocks, 0); + run_flash_test(state, -mtd_read_failure_1_errno); +} + +static int mtd_erase_failure_1_eb; +static int mtd_erase_failure_1_errno; +static int mtd_erase_failure_1(libmtd_t desc, const struct mtd_dev_info *mtd, + int fd, int eb) +{ + if (eb == mtd_erase_failure_1_eb) { + errno = mtd_erase_failure_1_errno; + if (errno == EOPNOTSUPP) + default_mtd_erase(desc, mtd, fd, eb); + return -1; + } + return default_mtd_erase(desc, mtd, fd, eb); +} + +static void test_mtd_read_no_erase_empty_flash_bytes(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.type = MTD_NORFLASH; + test_init(); + memset(flash_memory, FLASH_EMPTY_BYTE, 16); + impl_mtd_erase = mtd_erase_failure_1; + mtd_erase_failure_1_eb = 0; + mtd_erase_failure_1_errno = ERANGE; /* dummy value */ + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_mtd_erase_not_supported(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_erase = mtd_erase_failure_1; + mtd_erase_failure_1_eb = 0; + mtd_erase_failure_1_errno = EOPNOTSUPP; + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static void test_mtd_erase_failure(void **state) +{ + test_init(); + impl_mtd_erase = mtd_erase_failure_1; + mtd_erase_failure_1_eb = 0; + mtd_erase_failure_1_errno = ERANGE; /* dummy value */ + copy_flash_state(); + clear_bit(expected_locked_blocks, 0); + run_flash_test(state, -mtd_erase_failure_1_errno); +} + +static void test_mtd_erase_failure_EIO(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_erase = mtd_erase_failure_1; + mtd_erase_failure_1_eb = 0; + mtd_erase_failure_1_errno = EIO; + + copy_flash_state(); + memcpy(expected_flash_memory + 16, image_buf, image.size); + for (int i = 0; i <= 3; i++) + clear_bit(expected_locked_blocks, i); + set_bit(expected_bad_blocks, 0); + + run_flash_test(state, 0); +} + +static void patch_image_buf_empty_bytes_2(unsigned char *buf) +{ + memset(buf, FLASH_EMPTY_BYTE, 16); +} + +static void test_no_mtd_write_empty_image_bytes(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + patch_image_buf = patch_image_buf_empty_bytes_2; + test_init(); + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 0; + mtd_write_failure_1_offs = 0; + mtd_write_failure_1_errno = ERANGE; /* dummy value */ + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + for (int i = 0; i <= 2; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 0); + clear_bit(expected_written_pages, 1); + + run_flash_test(state, 0); +} + +static void test_mtd_write_failure(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = ERANGE; /* dummy value */ + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, 24); + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); + for (int i = 0; i <= 1; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 3); + + run_flash_test(state, -mtd_write_failure_1_errno); +} + +static void test_mtd_write_bad_block(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = EIO; + + copy_flash_state(); + set_bit(expected_bad_blocks, 1); + clear_bit(expected_written_pages, 2); + clear_bit(expected_written_pages, 3); + memcpy(expected_flash_memory, image_buf, 16); + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); + memcpy(expected_flash_memory + 32, image_buf + 16, 32); + for (int i = 0; i <= 3; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static int mtd_erase_failure_2_eb; +static int mtd_erase_failure_2_idx; +static int mtd_erase_failure_2_errno; +static int mtd_erase_failure_2(libmtd_t desc, const struct mtd_dev_info *mtd, + int fd, int eb) +{ + check_args(mtd, fd, eb); + if (eb == mtd_erase_failure_2_eb) { + if (mtd_erase_failure_2_idx == 0) { + errno = mtd_erase_failure_2_errno; + return -1; + } + --mtd_erase_failure_2_idx; + } + return default_mtd_erase(desc, mtd, fd, eb); +} + +static void test_mtd_write_bad_block_erase_failure(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = EIO; + + impl_mtd_erase = mtd_erase_failure_2; + mtd_erase_failure_2_eb = 1; + mtd_erase_failure_2_idx = 1; + mtd_erase_failure_2_errno = ERANGE; /* dummy value */ + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, 24); + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); + for (int i = 0; i <= 1; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 3); + + run_flash_test(state, -mtd_erase_failure_2_errno); +} + +static void test_mtd_write_bad_block_erase_failure_EIO(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = EIO; + + impl_mtd_erase = mtd_erase_failure_2; + mtd_erase_failure_2_eb = 1; + mtd_erase_failure_2_idx = 1; + mtd_erase_failure_2_errno = EIO; + + copy_flash_state(); + set_bit(expected_bad_blocks, 1); + clear_bit(expected_written_pages, 3); + memcpy(expected_flash_memory, image_buf, 24); + memset(expected_flash_memory + 24, FLASH_EMPTY_BYTE, 8); + memcpy(expected_flash_memory + 32, image_buf + 16, 32); + for (int i = 0; i <= 3; i++) + clear_bit(expected_locked_blocks, i); + + run_flash_test(state, 0); +} + +static int mtd_mark_bad_failure_1_errno; +static int mtd_mark_bad_failure_1(const struct mtd_dev_info *mtd, int fd, + int eb) +{ + check_args(mtd, fd, eb); + errno = mtd_mark_bad_failure_1_errno; + return -1; +} + +static void test_mtd_write_bad_block_mark_not_supported(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = EIO; + + impl_mtd_mark_bad = mtd_mark_bad_failure_1; + mtd_mark_bad_failure_1_errno = EOPNOTSUPP; + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, 16); + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); + memcpy(expected_flash_memory + 32, image_buf + 16, 32); + + for (int i = 0; i <= 3; i++) + clear_bit(expected_locked_blocks, i); + for (int i = 2; i <= 3; i++) + clear_bit(expected_written_pages, i); + + run_flash_test(state, 0); +} + +static void test_mtd_write_bad_block_mark_failure(void **state) +{ + image.seek = 0; + image.size = 48; + mtd_ubi_info_s.mtd.size = 1024; + mtd_ubi_info_s.mtd.eb_size = 16; + mtd_ubi_info_s.mtd.min_io_size = 8; + test_init(); + + impl_mtd_write = mtd_write_failure_1; + mtd_write_failure_1_eb = 1; + mtd_write_failure_1_offs = 8; + mtd_write_failure_1_errno = EIO; + + impl_mtd_mark_bad = mtd_mark_bad_failure_1; + mtd_mark_bad_failure_1_errno = ERANGE; /* dummy value */ + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, 16); + memset(expected_flash_memory + 16, FLASH_EMPTY_BYTE, 16); + for (int i = 0; i <= 1; i++) + clear_bit(expected_locked_blocks, i); + for (int i = 2; i <= 3; i++) + clear_bit(expected_written_pages, i); + + run_flash_test(state, -mtd_mark_bad_failure_1_errno); +} + +static void test_multiple_callbacks(void **state) +{ + /* flash_write() is typically executed with (len <= 16 * 1024) */ + image.seek = 0; + image.size = 63 * 1024; + mtd_ubi_info_s.mtd.size = 64 * 1024; + mtd_ubi_info_s.mtd.eb_size = 8 * 1024; + mtd_ubi_info_s.mtd.min_io_size = 1024; + test_init(); + + copy_flash_state(); + memcpy(expected_flash_memory, image_buf, image.size); + memset(expected_flash_memory + 63 * 1024, FLASH_EMPTY_BYTE, 1024); + for (int i = 0; i <= 7; i++) + clear_bit(expected_locked_blocks, i); + clear_bit(expected_written_pages, 63); + + run_flash_test(state, 0); +} + +#define TEST(name) \ + cmocka_unit_test_setup_teardown(test_##name, test_setup, test_teardown) + +int main(void) +{ + static const struct CMUnitTest tests[] = { + TEST(simple), + TEST(simple_NOR), + TEST(padding_less_than_page), + TEST(padding_page), + TEST(skip_write_page_empty_bytes), + TEST(padding_more_than_page), + TEST(seek), + TEST(seek_not_multiple_of_eb_size), + TEST(not_enough_flash), + TEST(invalid_mtd_device), + TEST(invalid_image_size), + TEST(empty_image), + TEST(mtd_dev_open_failure), + TEST(malloc_failure), + TEST(skip_bad_blocks), + TEST(too_many_known_bad_blocks), + TEST(too_many_unknown_bad_blocks), + TEST(mtd_is_bad_not_supported), + TEST(mtd_is_bad_failure), + TEST(mtd_is_locked_not_supported), + TEST(mtd_is_locked_failure), + TEST(mtd_unlock_not_supported), + TEST(mtd_unlock_failure), + TEST(mtd_read_failure), + TEST(mtd_read_no_erase_empty_flash_bytes), + TEST(mtd_erase_not_supported), + TEST(mtd_erase_failure), + TEST(mtd_erase_failure_EIO), + TEST(no_mtd_write_empty_image_bytes), + TEST(mtd_write_failure), + TEST(mtd_write_bad_block), + TEST(mtd_write_bad_block_erase_failure), + TEST(mtd_write_bad_block_erase_failure_EIO), + TEST(mtd_write_bad_block_mark_not_supported), + TEST(mtd_write_bad_block_mark_failure), + TEST(multiple_callbacks), + }; + return cmocka_run_group_tests_name("flash_handler", tests, group_setup, + group_teardown); +}