/**************************************************************************** * apps/testing/mtd_config_fs/mtd_config_fs_test_main.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ #define TEST_KEY1 "testkey1" #define TEST_KEY2 "testkey2" #define TEST_DATA1 "abc" #define TEST_DATA2 "def" /**************************************************************************** * Private Data ****************************************************************************/ /* Allocation Table Entry */ begin_packed_struct struct nvs_ate { uint32_t id; /* data id */ uint16_t offset; /* data offset within sector */ uint16_t len; /* data len within sector */ uint16_t key_len; /* key string len */ uint8_t part; /* part of a multipart data - future extension */ uint8_t crc8; /* crc8 check of the ate entry */ uint8_t expired; /* 0xFF-newest entry, others-old entry */ uint8_t reserved[3]; /* for future extension */ } end_packed_struct; /* Pre-allocated simulated flash */ struct mtdnvs_ctx_s { char mountdir[CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_MAXNAME]; struct mallinfo mmbefore; struct mallinfo mmprevious; struct mallinfo mmafter; }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: fnv_hash_32 ****************************************************************************/ static uint32_t nvs_fnv_hash(FAR const uint8_t *input, uint32_t len) { uint32_t i = 0; uint32_t hval = 2166136261; /* FNV-1 hash each octet in the buffer */ while (i++ < len) { /* multiply by the 32 bit FNV magic prime mod 2^32 */ hval *= 0x01000193; /* xor the bottom with the current octet */ hval ^= *input++; } return hval; } /**************************************************************************** * Name: fill_crc8_update ****************************************************************************/ static void fill_crc8_update(FAR struct nvs_ate *entry) { uint8_t ate_crc; ate_crc = crc8part((FAR const uint8_t *)entry, offsetof(struct nvs_ate, crc8), 0xff); entry->crc8 = ate_crc; } /**************************************************************************** * Name: fill_gc_done_ate ****************************************************************************/ static void fill_corrupted_close_ate(FAR struct nvs_ate *close_ate) { memset((FAR void *)close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(struct nvs_ate)); close_ate->id = 0xffffffff; close_ate->len = 0U; } /**************************************************************************** * Name: fill_close_ate ****************************************************************************/ static void fill_close_ate(FAR struct nvs_ate *close_ate, int offset) { memset((FAR void *)close_ate, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(struct nvs_ate)); close_ate->id = 0xffffffff; close_ate->len = 0U; close_ate->offset = offset; fill_crc8_update(close_ate); } /**************************************************************************** * Name: fill_gc_done_ate ****************************************************************************/ static void fill_gc_done_ate(FAR struct nvs_ate *gc_done_ate) { memset((FAR void *)gc_done_ate, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(struct nvs_ate)); gc_done_ate->id = 0xffffffff; gc_done_ate->len = 0U; fill_crc8_update(gc_done_ate); } /**************************************************************************** * Name: fill_ate ****************************************************************************/ static void fill_ate(FAR struct nvs_ate *ate, FAR const char *key, uint16_t len, uint16_t offset, bool expired) { memset((FAR void *)ate, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(struct nvs_ate)); ate->id = nvs_fnv_hash((FAR const uint8_t *)key, strlen(key) + 1) % 0xfffffffd + 1; ate->len = len; ate->offset = offset; ate->key_len = strlen(key) + 1; fill_crc8_update(ate); ate->expired = expired ? 0x7f : 0xff; } /**************************************************************************** * Name: fill_corrupted_ate ****************************************************************************/ static void fill_corrupted_ate(FAR struct nvs_ate *ate, FAR const char *key, uint16_t len, uint16_t offset) { memset((FAR void *)ate, CONFIG_MTD_CONFIG_ERASEDVALUE, sizeof(struct nvs_ate)); ate->id = nvs_fnv_hash((FAR const uint8_t *)key, strlen(key) + 1) % 0xfffffffd + 1; ate->len = len; ate->offset = offset; ate->key_len = strlen(key) + 1; } /**************************************************************************** * Name: mtdnvs_showmemusage ****************************************************************************/ static void mtdnvs_showmemusage(struct mallinfo *mmbefore, struct mallinfo *mmafter) { printf("VARIABLE BEFORE AFTER DELTA\n"); printf("======== ======== ======== ========\n"); printf("arena %8x %8x %8x\n", mmbefore->arena , mmafter->arena, mmafter->arena - mmbefore->arena); printf("ordblks %8d %8d %8d\n", mmbefore->ordblks , mmafter->ordblks, mmafter->ordblks - mmbefore->ordblks); printf("mxordblk %8x %8x %8x\n", mmbefore->mxordblk, mmafter->mxordblk, mmafter->mxordblk - mmbefore->mxordblk); printf("uordblks %8x %8x %8x\n", mmbefore->uordblks, mmafter->uordblks, mmafter->uordblks - mmbefore->uordblks); printf("fordblks %8x %8x %8x\n", mmbefore->fordblks, mmafter->fordblks, mmafter->fordblks - mmbefore->fordblks); } /**************************************************************************** * Name: mtdnvs_endmemusage ****************************************************************************/ static void mtdnvs_endmemusage(FAR struct mtdnvs_ctx_s *ctx) { ctx->mmafter = mallinfo(); printf("\nFinal memory usage:\n"); mtdnvs_showmemusage(&ctx->mmbefore, &ctx->mmafter); } /**************************************************************************** * Name: show_useage ****************************************************************************/ static void show_useage(void) { printf("Usage : mtdconfig_fs_test [OPTION [ARG]] ...\n"); printf("-h show this help statement\n"); printf("-m mount point to be tested e.g. [%s]\n", CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME); } /**************************************************************************** * Name: teardown ****************************************************************************/ static int teardown(void) { int fd; int ret; fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); return -errno; } ret = ioctl(fd, MTDIOC_BULKERASE, NULL); if (ret < 0) { printf("%s:clear failed, ret=%d\n", __func__, ret); return -errno; } close(fd); ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:mtdconfig_unregister failed, ret=%d\n", __func__, ret); return ret; } return ret; } extern int find_mtddriver(FAR const char *pathname, FAR struct inode **ppinode); /**************************************************************************** * Name: setup ****************************************************************************/ static int setup(struct mtdnvs_ctx_s *ctx) { int ret; FAR struct inode *sys_node; ret = find_mtddriver(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, &sys_node); if (ret < 0) { printf("ERROR: open %s failed: %d\n", CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, ret); return -errno; } ret = mtdconfig_register(sys_node->u.i_mtd); if (ret < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, ret); return ret; } return ret; } /**************************************************************************** * Name: test_nvs_mount ****************************************************************************/ static void test_nvs_mount(struct mtdnvs_ctx_s *ctx) { int ret; printf("%s: test begin\n", __func__); ret = setup(ctx); if (ret < 0) { printf("%s:mtdconfig_register failed, ret=%d\n", __func__, ret); goto test_fail; } /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: execute_long_pattern_write ****************************************************************************/ static int execute_long_pattern_write(const char *key) { struct config_data_s data; int i; int fd; int ret; uint8_t rd_buf[512]; uint8_t wr_buf[512]; char pattern[4]; pattern[0] = 0xde; pattern[1] = 0xad; pattern[2] = 0xbe; pattern[3] = 0xef; fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); return -errno; } strlcpy(data.name, key, sizeof(data.name)); data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (ret != -1 || errno != ENOENT) { printf("%s:CFGDIOC_GETCONFIG unexpected failure: %d\n", __func__, ret); goto err_fd; } for (i = 0; i < sizeof(wr_buf); i += sizeof(pattern)) { memcpy(wr_buf + i, pattern, sizeof(pattern)); } data.configdata = wr_buf; data.len = sizeof(wr_buf); ret = ioctl(fd, CFGDIOC_SETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto err_fd; } data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto err_fd; } if (memcmp(wr_buf, rd_buf, sizeof(rd_buf)) != 0) { printf("%s:RD buff should be equal to the WR buff\n", __func__); ret = -EIO; goto err_fd; } err_fd: close(fd); return ret; } /**************************************************************************** * Name: test_nvs_write ****************************************************************************/ static void test_nvs_write(struct mtdnvs_ctx_s *ctx) { int ret; printf("%s: test begin\n", __func__); ret = setup(ctx); if (ret < 0) { printf("%s:mtdconfig_register failed, ret=%d\n", __func__, ret); goto test_fail; } ret = execute_long_pattern_write(TEST_KEY1); if (ret < 0) { printf("%s:execute_long_pattern_write failed, ret=%d\n", __func__, ret); goto test_fail; } /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_corrupt_expire * Description: Test that startup correctly handles invalid expire. ****************************************************************************/ static void test_nvs_corrupt_expire(struct mtdnvs_ctx_s *ctx) { struct nvs_ate ate; int mtd_fd = -1; int nvs_fd = -1; int ret; int i; uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE; struct config_data_s data; char rd_buf[50]; printf("%s: test begin\n", __func__); mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR); if (mtd_fd < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd); goto test_fail; } /* write valid data */ ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1); if (ret < 0) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } ret = write(mtd_fd, TEST_DATA1, strlen(TEST_DATA1) + 1); if (ret < 0) { printf("%s:write data1 failed, ret=%d\n", __func__, ret); goto test_fail; } /* write valid data again, simulate overwrite data */ ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1); if (ret < 0) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } ret = write(mtd_fd, TEST_DATA2, strlen(TEST_DATA2) + 1); if (ret < 0) { printf("%s:write data2 failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff */ for (i = 2 * (strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2); i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* Write ate */ fill_ate(&ate, TEST_KEY1, strlen(TEST_DATA2) + 1, strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2, false); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } fill_ate(&ate, TEST_KEY1, strlen(TEST_DATA1) + 1, 0, false); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write gc_done ate */ fill_gc_done_ate(&ate); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write close ate, mark section 0 as closed */ fill_close_ate(&ate, CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write close ate failed, ret=%d\n", __func__, ret); goto test_fail; } close(mtd_fd); mtd_fd = -1; ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } nvs_fd = open("/dev/config", 0); if (nvs_fd < 0) { printf("%s:open failed, ret=%d\n", __func__, nvs_fd); goto test_fail; } strlcpy(data.name, TEST_KEY1, sizeof(data.name)); data.configdata = (FAR uint8_t *)rd_buf; data.len = sizeof(rd_buf); ret = ioctl(nvs_fd, CFGDIOC_FIRSTCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_FIRSTCONFIG unexpected failure: %d\n", __func__, ret); goto test_fail; } if (strncmp(rd_buf, TEST_DATA2, sizeof(rd_buf)) != 0) { printf("%s:unexpected value\n", __func__); goto test_fail; } ret = ioctl(nvs_fd, CFGDIOC_NEXTCONFIG, &data); if (ret != -1 || errno != ENOENT) { printf("%s:CFGDIOC_NEXTCONFIG should return ENOENT, but: %d\n", __func__, ret); goto test_fail; } close(nvs_fd); nvs_fd = -1; /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (mtd_fd >= 0) close(mtd_fd); if (nvs_fd >= 0) close(nvs_fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_corrupted_write ****************************************************************************/ static void test_nvs_corrupted_write(struct mtdnvs_ctx_s *ctx) { int ret; char rd_buf[512]; char wr_buf_1[] = TEST_DATA1; char wr_buf_2[] = TEST_DATA2; char key1[] = TEST_KEY1; int mtd_fd = -1; int nvs_fd = -1; int i; uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE; struct nvs_ate ate; struct config_data_s data; printf("%s: test begin\n", __func__); mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR); if (mtd_fd < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd); goto test_fail; } /* lets simulate loss of part of data */ /* write valid data */ ret = write(mtd_fd, key1, sizeof(key1)); if (ret != sizeof(key1)) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } ret = write(mtd_fd, wr_buf_1, sizeof(wr_buf_1)); if (ret != sizeof(wr_buf_1)) { printf("%s:write data1 failed, ret=%d\n", __func__, ret); goto test_fail; } /* power loss occurs after we write data */ ret = write(mtd_fd, key1, sizeof(key1)); if (ret != sizeof(key1)) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } ret = write(mtd_fd, wr_buf_2, sizeof(wr_buf_1)); if (ret != sizeof(wr_buf_2)) { printf("%s:write data2 failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff */ for (i = 2 * (sizeof(key1) + sizeof(wr_buf_1)); i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 3 * 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write ate */ fill_ate(&ate, key1, sizeof(wr_buf_1), 0, false); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write gc_done ate */ fill_gc_done_ate(&ate); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } close(mtd_fd); mtd_fd = -1; /* now start up */ ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } nvs_fd = open("/dev/config", 0); if (nvs_fd < 0) { printf("%s:open failed, ret=%d\n", __func__, nvs_fd); goto test_fail; } strlcpy(data.name, TEST_KEY1, sizeof(data.name)); data.configdata = (FAR uint8_t *)rd_buf; data.len = sizeof(rd_buf); ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data); if (ret < 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); goto test_fail; } if (strncmp(rd_buf, wr_buf_1, sizeof(rd_buf)) != 0) { printf("%s:failed, RD buff should be equal to the first WR buff " "because subsequent write operation has failed\n", __func__); goto test_fail; } close(nvs_fd); nvs_fd = -1; /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: printf("%s: failed\n", __func__); if (nvs_fd > 0) { close(nvs_fd); } if (mtd_fd > 0) { close(mtd_fd); } return; } /**************************************************************************** * Name: test_nvs_gc ****************************************************************************/ static void test_nvs_gc(struct mtdnvs_ctx_s *ctx) { int ret; int fd = -1; uint8_t buf[44]; uint8_t rd_buf[44]; struct config_data_s data; uint16_t i; uint16_t id; const uint16_t max_id = 10; /* 4096 * 2 / (44 + 4 + 16) = 128, 129 write will trigger GC. */ const uint16_t max_writes = 129; printf("%s: test begin\n", __func__); ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); goto test_fail; } for (i = 0; i < max_writes; i++) { id = (i % max_id); uint8_t id_data = id + max_id * (i / max_id); memset(buf, id_data, sizeof(buf)); /* 4 byte key */ sprintf(data.name, "k%02d", id); data.configdata = buf; data.len = sizeof(buf); ret = ioctl(fd, CFGDIOC_SETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } } for (id = 0; id < max_id; id++) { /* 4 byte key */ sprintf(data.name, "k%02d", id); data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } for (i = 0; i < sizeof(rd_buf); i++) { rd_buf[i] = rd_buf[i] % max_id; buf[i] = id; } if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0) { printf("RD buff should be equal to the WR buff\n"); goto test_fail; } } close(fd); fd = -1; ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:mtdconfig_unregister failed, ret=%d\n", __func__, ret); goto test_fail; } ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); goto test_fail; } for (id = 0; id < max_id; id++) { /* 4 byte key */ sprintf(data.name, "k%02d", id); data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } for (i = 0; i < sizeof(rd_buf); i++) { rd_buf[i] = rd_buf[i] % max_id; buf[i] = id; } if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0) { printf("RD buff should be equal to the WR buff\n"); goto test_fail; } } close(fd); fd = -1; /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (fd >= 0) close(fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: write_content ****************************************************************************/ static int write_content(uint16_t max_id, uint16_t begin, uint16_t end) { uint8_t buf[44]; int fd = -1; int ret; struct config_data_s data; uint16_t i; fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); return -errno; } for (i = begin; i < end; i++) { uint8_t id = (i % max_id); uint8_t id_data = id + max_id * (i / max_id); memset(buf, id_data, sizeof(buf)); /* 4 byte key */ sprintf(data.name, "k%02d", id); data.configdata = buf; data.len = sizeof(buf); ret = ioctl(fd, CFGDIOC_SETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } } close(fd); return ret; test_fail: if (fd >= 0) close(fd); return ret; } /**************************************************************************** * Name: check_content ****************************************************************************/ static int check_content(uint16_t max_id) { uint8_t rd_buf[44]; uint8_t buf[44]; int fd = -1; int ret; struct config_data_s data; fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); return -errno; } for (uint16_t id = 0; id < max_id; id++) { /* 4 byte key */ sprintf(data.name, "k%02d", id); data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } for (uint16_t i = 0; i < sizeof(rd_buf); i++) { rd_buf[i] = rd_buf[i] % max_id; buf[i] = id; } if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0) { printf("RD buff should be equal to the WR buff\n"); goto test_fail; } } close(fd); return ret; test_fail: if (fd >= 0) close(fd); return ret; } /**************************************************************************** * Name: test_nvs_gc_3sectors * Description: Full round of GC over 3 sectors ****************************************************************************/ static void test_nvs_gc_3sectors(struct mtdnvs_ctx_s *ctx) { int ret; const uint16_t max_id = 64; /* 4096 * 2 / (44 + 4 + 16) = 128, 129 write will trigger GC. */ const uint16_t max_writes = 129; /* 4096 / (44 + 4 + 16) = 64, 129 + 64 write will trigger 2st GC. */ const uint16_t max_writes_2 = 129 + 64; const uint16_t max_writes_3 = 129 + 64 + 64; const uint16_t max_writes_4 = 129 + 64 + 64 + 64; printf("%s: test begin\n", __func__); ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } /* Trigger 1st GC */ ret = write_content(max_id, 0, max_writes); if (ret < 0) { printf("%s:1st GC write failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:1st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:1st mtdconfig_unregister failed, ret=%d\n", __func__, ret); goto test_fail; } ret = setup(ctx); if (ret < 0) { printf("%s:1st setup failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:1st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } /* Trigger 2nd GC */ ret = write_content(max_id, max_writes, max_writes_2); if (ret < 0) { printf("%s:2st GC write failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:2st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:2st mtdconfig_unregister failed, ret=%d\n", __func__, ret); goto test_fail; } ret = setup(ctx); if (ret < 0) { printf("%s:2st setup failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:2st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } /* Trigger 3rd GC */ ret = write_content(max_id, max_writes_2, max_writes_3); if (ret < 0) { printf("%s:3st GC write failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:3st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:3st mtdconfig_unregister failed, ret=%d\n", __func__, ret); goto test_fail; } ret = setup(ctx); if (ret < 0) { printf("%s:3st setup failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:3st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } /* Trigger 4th GC */ ret = write_content(max_id, max_writes_3, max_writes_4); if (ret < 0) { printf("%s:4st GC write failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:4st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } ret = mtdconfig_unregister(); if (ret < 0) { printf("%s:4st mtdconfig_unregister failed, ret=%d\n", __func__, ret); goto test_fail; } ret = setup(ctx); if (ret < 0) { printf("%s:4st setup failed, ret=%d\n", __func__, ret); goto test_fail; } ret = check_content(max_id); if (ret < 0) { printf("%s:4st GC check failed, ret=%d\n", __func__, ret); goto test_fail; } /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_corrupted_sector_close ****************************************************************************/ static void test_nvs_corrupted_sector_close(struct mtdnvs_ctx_s *ctx) { int ret; uint8_t rd_buf[512]; uint8_t wr_buf[512]; char key1[] = TEST_KEY1; int mtd_fd = -1; int nvs_fd = -1; int loop_section; int i; uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE; struct nvs_ate ate; struct config_data_s data; printf("%s: test begin\n", __func__); mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR); if (mtd_fd < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd); goto test_fail; } /* lets simulate loss of close at the beginning of gc */ for (loop_section = 0; loop_section < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 1; loop_section++) { /* write valid data */ for (i = 0; i < 2 ; i++) { ret = write(mtd_fd, key1, sizeof(key1)); if (ret != sizeof(key1)) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } memset(wr_buf, 'A' + i, sizeof(wr_buf)); wr_buf[sizeof(wr_buf) - 1] = '\0'; ret = write(mtd_fd, wr_buf, sizeof(wr_buf)); if (ret != sizeof(wr_buf)) { printf("%s:write data1 failed, ret=%d\n", __func__, ret); goto test_fail; } } /* set unused flash to 0xff */ for (i = 2 * (sizeof(key1) + sizeof(wr_buf)); i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write ate 2 times */ fill_ate(&ate, key1, sizeof(wr_buf), sizeof(wr_buf) + sizeof(key1), (loop_section == CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 2) ? false : true); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } fill_ate(&ate, key1, sizeof(wr_buf), 0, true); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write gc_done ate */ fill_gc_done_ate(&ate); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } if (loop_section == CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 2) { fill_corrupted_close_ate(&ate); } else { fill_close_ate(&ate, CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16); } ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write close ate failed, ret=%d\n", __func__, ret); goto test_fail; } } close(mtd_fd); mtd_fd = -1; ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } nvs_fd = open("/dev/config", 0); if (nvs_fd < 0) { printf("%s:open failed, ret=%d\n", __func__, nvs_fd); goto test_fail; } strlcpy(data.name, TEST_KEY1, sizeof(data.name)); data.configdata = rd_buf; data.len = sizeof(rd_buf); ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } if (memcmp(rd_buf, wr_buf, sizeof(rd_buf)) != 0) { printf("%s:strncmp failed\n", __func__); ret = -EACCES; goto test_fail; } /* Ensure that the NVS is able to store new content. */ if (execute_long_pattern_write(TEST_KEY2) != 0) { printf("%s:write again failed\n", __func__); ret = -EACCES; goto test_fail; } close(nvs_fd); nvs_fd = -1; /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (nvs_fd >= 0) close(nvs_fd); if (mtd_fd >= 0) close(mtd_fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_full_sector * Description: Test case when storage become full, * so only deletion is possible. ****************************************************************************/ static void test_nvs_full_sector(struct mtdnvs_ctx_s *ctx) { int ret; uint16_t filling_id = 0; uint16_t i; uint16_t data_read; int fd = -1; struct config_data_s data; printf("%s: test begin\n", __func__); ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); goto test_fail; } while (1) { sprintf(data.name, "k%04x", filling_id); data.configdata = (FAR uint8_t *)&filling_id; data.len = sizeof(filling_id); ret = ioctl(fd, CFGDIOC_SETCONFIG, &data); if (ret == -1 && errno == ENOSPC) { break; } else if (ret != 0) { printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } filling_id++; } /* check whether can delete whatever from full storage */ sprintf(data.name, "k%04x", 1); data.configdata = NULL; data.len = 0; ret = ioctl(fd, CFGDIOC_DELCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_DELCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } /* the last sector is full now, test re-initialization */ close(fd); fd = -1; mtdconfig_unregister(); ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } fd = open("/dev/config", 0); if (fd < 0) { printf("%s:open failed, ret=%d\n", __func__, fd); goto test_fail; } sprintf(data.name, "k%04x", filling_id); data.configdata = (FAR uint8_t *)&filling_id; data.len = sizeof(filling_id); ret = ioctl(fd, CFGDIOC_SETCONFIG, &data); if (ret != 0) { printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } /* sanitycheck on NVS content */ for (i = 0; i <= filling_id; i++) { sprintf(data.name, "k%04x", i); data.configdata = (FAR uint8_t *)&data_read; data.len = sizeof(data_read); ret = ioctl(fd, CFGDIOC_GETCONFIG, &data); if (i == 1) { if (ret != -1 || errno != ENOENT) { printf("%s:shouldn't found the entry: %d\n", __func__, i); ret = -EIO; goto test_fail; } } else if (ret != 0) { printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret); ret = -EIO; goto test_fail; } else { if (data_read != i) { printf("%s:read data %d \n", __func__, data_read); printf("%s:read expected %d\n", __func__, i); printf("%s:read unexpected data: %d instead of %d\n", __func__, data_read, i); ret = -EIO; goto test_fail; } } } close(fd); /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (fd >= 0) close(fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_gc_corrupt_close_ate * Description: Test that garbage-collection can * recover all ate's even when the last ate, ie close_ate, * is corrupt. In this test the close_ate is set to point to the * last ate at -5. A valid ate is however present at -6. * Since the close_ate has an invalid crc8, the offset * should not be used and a recover of the * last ate should be done instead. ****************************************************************************/ static void test_nvs_gc_corrupt_close_ate(struct mtdnvs_ctx_s *ctx) { struct nvs_ate ate; struct nvs_ate close_ate; int mtd_fd = -1; int nvs_fd = -1; int ret; int i; uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE; struct config_data_s data; char rd_buf[50]; printf("%s: test begin\n", __func__); mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR); if (mtd_fd < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd); goto test_fail; } /* write valid data */ ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1); if (ret != strlen(TEST_KEY1) + 1) { printf("%s:write key1 failed, ret=%d\n", __func__, ret); goto test_fail; } ret = write(mtd_fd, TEST_DATA1, strlen(TEST_DATA1) + 1); if (ret != strlen(TEST_DATA1) + 1) { printf("%s:write data1 failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff */ for (i = strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 6 * 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* Write valid ate at -6 */ fill_ate(&ate, TEST_KEY1, strlen(TEST_DATA1) + 1, 0, false); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff */ for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 5 * 16; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 2 * 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write gc_done ate */ fill_gc_done_ate(&ate); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write invalid close ate, mark section 0 as closed */ fill_corrupted_close_ate(&close_ate); ret = write(mtd_fd, &close_ate, sizeof(close_ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff in section 1 */ for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write invalid close ate, mark section 1 as closed */ fill_corrupted_close_ate(&close_ate); ret = write(mtd_fd, &close_ate, sizeof(close_ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } close(mtd_fd); mtd_fd = -1; ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } nvs_fd = open("/dev/config", 0); if (nvs_fd < 0) { printf("%s:open failed, ret=%d\n", __func__, nvs_fd); goto test_fail; } strlcpy(data.name, TEST_KEY1, sizeof(data.name)); data.configdata = (FAR uint8_t *)rd_buf; data.len = sizeof(rd_buf); ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data); if (ret != 0) { printf("%s:NVSIOC_READ unexpected failure: %d\n", __func__, ret); goto test_fail; } if (strncmp(rd_buf, TEST_DATA1, sizeof(rd_buf)) != 0) { printf("%s:unexpected value\n", __func__); goto test_fail; } close(nvs_fd); nvs_fd = -1; /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (nvs_fd >= 0) close(nvs_fd); if (mtd_fd >= 0) close(mtd_fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Name: test_nvs_gc_corrupt_ate * Description: Test that garbage-collection correctly handles corrupt ate's. ****************************************************************************/ static void test_nvs_gc_corrupt_ate(struct mtdnvs_ctx_s *ctx) { struct nvs_ate ate; struct nvs_ate close_ate; int mtd_fd = -1; int ret; int i; uint8_t erase_value = CONFIG_MTD_CONFIG_ERASEDVALUE; printf("%s: test begin\n", __func__); fill_corrupted_ate(&ate, TEST_KEY1, 10, 0); mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR); if (mtd_fd < 0) { printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd); goto test_fail; } /* set unused flash to 0xff */ for (i = 0 ; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* Write invalid ate */ ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write ate failed, ret=%d\n", __func__, ret); goto test_fail; } for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2 + 16; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 32; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write gc_done ate */ fill_gc_done_ate(&ate); ret = write(mtd_fd, &ate, sizeof(ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* write close ate, mark section 0 as closed */ fill_close_ate(&close_ate, CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2); ret = write(mtd_fd, &close_ate, sizeof(close_ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } /* set unused flash to 0xff in section 1 */ for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE; i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 16; i++) { ret = write(mtd_fd, &erase_value, sizeof(erase_value)); if (ret != sizeof(erase_value)) { printf("%s:erase failed, ret=%d\n", __func__, ret); goto test_fail; } } /* write close ate, mark section 1 as closed */ ret = write(mtd_fd, &close_ate, sizeof(close_ate)); if (ret != sizeof(ate)) { printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret); goto test_fail; } close(mtd_fd); mtd_fd = -1; ret = setup(ctx); if (ret < 0) { printf("%s:setup failed, ret=%d\n", __func__, ret); goto test_fail; } /* at the end of test, erase all blocks */ ret = teardown(); if (ret < 0) { printf("%s:teardown failed, ret=%d\n", __func__, ret); goto test_fail; } printf("%s: success\n", __func__); return; test_fail: if (mtd_fd >= 0) close(mtd_fd); printf("%s: failed\n", __func__); return; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: fstest_main ****************************************************************************/ int main(int argc, FAR char *argv[]) { FAR struct mtdnvs_ctx_s *ctx; int option; ctx = malloc(sizeof(struct mtdnvs_ctx_s)); if (ctx == NULL) { printf("malloc ctx feild,exit!\n"); exit(1); } memset(ctx, 0, sizeof(struct mtdnvs_ctx_s)); strlcpy(ctx->mountdir, CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, sizeof(ctx->mountdir)); /* Opt Parse */ while ((option = getopt(argc, argv, ":m:hn:")) != -1) { switch (option) { case 'm': strcpy(ctx->mountdir, optarg); break; case 'h': show_useage(); free(ctx); exit(0); case ':': printf("Error: Missing required argument\n"); free(ctx); exit(1); case '?': printf("Error: Unrecognized option\n"); free(ctx); exit(1); } } /* Set up memory monitoring */ ctx->mmbefore = mallinfo(); ctx->mmprevious = ctx->mmbefore; test_nvs_mount(ctx); test_nvs_write(ctx); test_nvs_corrupt_expire(ctx); test_nvs_corrupted_write(ctx); test_nvs_gc(ctx); test_nvs_gc_3sectors(ctx); test_nvs_corrupted_sector_close(ctx); test_nvs_full_sector(ctx); test_nvs_gc_corrupt_close_ate(ctx); test_nvs_gc_corrupt_ate(ctx); /* Show memory usage */ mtdnvs_endmemusage(ctx); fflush(stdout); free(ctx); return 0; }