blob: 11f18be8cdd1fc3baafef81cfdc09bf1a2bf008f [file] [log] [blame]
/*
* Copyright (c) 2019 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This test is designed to be run using flash-simulator which provide
* functionality for flash property customization and emulating errors in
* flash operation in parallel to regular flash API.
* Test should be run on qemu_x86 or native_posix target.
*/
#if !defined(CONFIG_BOARD_QEMU_X86) && !defined(CONFIG_BOARD_NATIVE_POSIX)
#error "Run on qemu_x86 or native_posix only"
#endif
#include <stdio.h>
#include <string.h>
#include <zephyr/ztest.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/stats/stats.h>
#include <zephyr/sys/crc.h>
#include <zephyr/fs/nvs.h>
#include "nvs_priv.h"
#define TEST_NVS_FLASH_AREA storage_partition
#define TEST_NVS_FLASH_AREA_OFFSET FIXED_PARTITION_OFFSET(TEST_NVS_FLASH_AREA)
#define TEST_NVS_FLASH_AREA_ID FIXED_PARTITION_ID(TEST_NVS_FLASH_AREA)
#define TEST_NVS_FLASH_AREA_DEV \
DEVICE_DT_GET(DT_MTD_FROM_FIXED_PARTITION(DT_NODELABEL(TEST_NVS_FLASH_AREA)))
#define TEST_DATA_ID 1
#define TEST_SECTOR_COUNT 5U
static const struct device *const flash_dev = TEST_NVS_FLASH_AREA_DEV;
struct nvs_fixture {
struct nvs_fs fs;
struct stats_hdr *sim_stats;
struct stats_hdr *sim_thresholds;
};
static void *setup(void)
{
int err;
const struct flash_area *fa;
struct flash_pages_info info;
static struct nvs_fixture fixture;
__ASSERT_NO_MSG(device_is_ready(flash_dev));
err = flash_area_open(TEST_NVS_FLASH_AREA_ID, &fa);
zassert_true(err == 0, "flash_area_open() fail: %d", err);
fixture.fs.offset = TEST_NVS_FLASH_AREA_OFFSET;
err = flash_get_page_info_by_offs(flash_area_get_device(fa), fixture.fs.offset,
&info);
zassert_true(err == 0, "Unable to get page info: %d", err);
fixture.fs.sector_size = info.size;
fixture.fs.sector_count = TEST_SECTOR_COUNT;
fixture.fs.flash_device = flash_area_get_device(fa);
return &fixture;
}
static void before(void *data)
{
struct nvs_fixture *fixture = (struct nvs_fixture *)data;
fixture->sim_stats = stats_group_find("flash_sim_stats");
fixture->sim_thresholds = stats_group_find("flash_sim_thresholds");
/* Verify if NVS is initialized. */
if (fixture->fs.ready) {
int err;
err = nvs_clear(&fixture->fs);
zassert_true(err == 0, "nvs_clear call failure: %d", err);
}
}
static void after(void *data)
{
struct nvs_fixture *fixture = (struct nvs_fixture *)data;
if (fixture->sim_stats) {
stats_reset(fixture->sim_stats);
}
if (fixture->sim_thresholds) {
stats_reset(fixture->sim_thresholds);
}
fixture->fs.sector_count = TEST_SECTOR_COUNT;
}
ZTEST_SUITE(nvs, NULL, setup, before, after, NULL);
ZTEST_F(nvs, test_nvs_mount)
{
int err;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
}
static void execute_long_pattern_write(uint16_t id, struct nvs_fs *fs)
{
char rd_buf[512];
char wr_buf[512];
char pattern[] = {0xDE, 0xAD, 0xBE, 0xEF};
size_t len;
len = nvs_read(fs, id, rd_buf, sizeof(rd_buf));
zassert_true(len == -ENOENT, "nvs_read unexpected failure: %d", len);
BUILD_ASSERT((sizeof(wr_buf) % sizeof(pattern)) == 0);
for (int i = 0; i < sizeof(wr_buf); i += sizeof(pattern)) {
memcpy(wr_buf + i, pattern, sizeof(pattern));
}
len = nvs_write(fs, id, wr_buf, sizeof(wr_buf));
zassert_true(len == sizeof(wr_buf), "nvs_write failed: %d", len);
len = nvs_read(fs, id, rd_buf, sizeof(rd_buf));
zassert_true(len == sizeof(rd_buf), "nvs_read unexpected failure: %d",
len);
zassert_mem_equal(wr_buf, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the WR buff");
}
ZTEST_F(nvs, test_nvs_write)
{
int err;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
execute_long_pattern_write(TEST_DATA_ID, &fixture->fs);
}
static int flash_sim_write_calls_find(struct stats_hdr *hdr, void *arg,
const char *name, uint16_t off)
{
if (!strcmp(name, "flash_write_calls")) {
uint32_t **flash_write_stat = (uint32_t **) arg;
*flash_write_stat = (uint32_t *)((uint8_t *)hdr + off);
}
return 0;
}
static int flash_sim_max_write_calls_find(struct stats_hdr *hdr, void *arg,
const char *name, uint16_t off)
{
if (!strcmp(name, "max_write_calls")) {
uint32_t **max_write_calls = (uint32_t **) arg;
*max_write_calls = (uint32_t *)((uint8_t *)hdr + off);
}
return 0;
}
ZTEST_F(nvs, test_nvs_corrupted_write)
{
int err;
size_t len;
char rd_buf[512];
char wr_buf_1[512];
char wr_buf_2[512];
char pattern_1[] = {0xDE, 0xAD, 0xBE, 0xEF};
char pattern_2[] = {0x03, 0xAA, 0x85, 0x6F};
uint32_t *flash_write_stat;
uint32_t *flash_max_write_calls;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
err = nvs_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf));
zassert_true(err == -ENOENT, "nvs_read unexpected failure: %d", err);
BUILD_ASSERT((sizeof(wr_buf_1) % sizeof(pattern_1)) == 0);
for (int i = 0; i < sizeof(wr_buf_1); i += sizeof(pattern_1)) {
memcpy(wr_buf_1 + i, pattern_1, sizeof(pattern_1));
}
len = nvs_write(&fixture->fs, TEST_DATA_ID, wr_buf_1, sizeof(wr_buf_1));
zassert_true(len == sizeof(wr_buf_1), "nvs_write failed: %d", len);
len = nvs_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf));
zassert_true(len == sizeof(rd_buf), "nvs_read unexpected failure: %d",
len);
zassert_mem_equal(wr_buf_1, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the first WR buff");
BUILD_ASSERT((sizeof(wr_buf_2) % sizeof(pattern_2)) == 0);
for (int i = 0; i < sizeof(wr_buf_2); i += sizeof(pattern_2)) {
memcpy(wr_buf_2 + i, pattern_2, sizeof(pattern_2));
}
/* Set the maximum number of writes that the flash simulator can
* execute.
*/
stats_walk(fixture->sim_thresholds, flash_sim_max_write_calls_find,
&flash_max_write_calls);
stats_walk(fixture->sim_stats, flash_sim_write_calls_find, &flash_write_stat);
*flash_max_write_calls = *flash_write_stat - 1;
*flash_write_stat = 0;
/* Flash simulator will lose part of the data at the end of this write.
* This should simulate power down during flash write. The written data
* are corrupted at this point and should be discarded by the NVS.
*/
len = nvs_write(&fixture->fs, TEST_DATA_ID, wr_buf_2, sizeof(wr_buf_2));
zassert_true(len == sizeof(wr_buf_2), "nvs_write failed: %d", len);
/* Reinitialize the NVS. */
memset(&fixture->fs, 0, sizeof(fixture->fs));
(void)setup();
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
len = nvs_read(&fixture->fs, TEST_DATA_ID, rd_buf, sizeof(rd_buf));
zassert_true(len == sizeof(rd_buf), "nvs_read unexpected failure: %d",
len);
zassert_true(memcmp(wr_buf_2, rd_buf, sizeof(rd_buf)) != 0,
"RD buff should not be equal to the second WR buff because of "
"corrupted write operation");
zassert_mem_equal(wr_buf_1, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the first WR buff because subsequent "
"write operation has failed");
}
ZTEST_F(nvs, test_nvs_gc)
{
int err;
int len;
uint8_t buf[32];
uint8_t rd_buf[32];
const uint16_t max_id = 10;
/* 25th write will trigger GC. */
const uint16_t max_writes = 26;
fixture->fs.sector_count = 2;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
for (uint16_t i = 0; i < max_writes; i++) {
uint8_t id = (i % max_id);
uint8_t id_data = id + max_id * (i / max_id);
memset(buf, id_data, sizeof(buf));
len = nvs_write(&fixture->fs, id, buf, sizeof(buf));
zassert_true(len == sizeof(buf), "nvs_write failed: %d", len);
}
for (uint16_t id = 0; id < max_id; id++) {
len = nvs_read(&fixture->fs, id, rd_buf, sizeof(buf));
zassert_true(len == sizeof(rd_buf),
"nvs_read unexpected failure: %d", len);
for (uint16_t i = 0; i < sizeof(rd_buf); i++) {
rd_buf[i] = rd_buf[i] % max_id;
buf[i] = id;
}
zassert_mem_equal(buf, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the WR buff");
}
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
for (uint16_t id = 0; id < max_id; id++) {
len = nvs_read(&fixture->fs, id, rd_buf, sizeof(buf));
zassert_true(len == sizeof(rd_buf),
"nvs_read unexpected failure: %d", len);
for (uint16_t i = 0; i < sizeof(rd_buf); i++) {
rd_buf[i] = rd_buf[i] % max_id;
buf[i] = id;
}
zassert_mem_equal(buf, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the WR buff");
}
}
static void write_content(uint16_t max_id, uint16_t begin, uint16_t end,
struct nvs_fs *fs)
{
uint8_t buf[32];
ssize_t len;
for (uint16_t 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));
len = nvs_write(fs, id, buf, sizeof(buf));
zassert_true(len == sizeof(buf), "nvs_write failed: %d", len);
}
}
static void check_content(uint16_t max_id, struct nvs_fs *fs)
{
uint8_t rd_buf[32];
uint8_t buf[32];
ssize_t len;
for (uint16_t id = 0; id < max_id; id++) {
len = nvs_read(fs, id, rd_buf, sizeof(buf));
zassert_true(len == sizeof(rd_buf),
"nvs_read unexpected failure: %d", len);
for (uint16_t i = 0; i < ARRAY_SIZE(rd_buf); i++) {
rd_buf[i] = rd_buf[i] % max_id;
buf[i] = id;
}
zassert_mem_equal(buf, rd_buf, sizeof(rd_buf),
"RD buff should be equal to the WR buff");
}
}
/**
* Full round of GC over 3 sectors
*/
ZTEST_F(nvs, test_nvs_gc_3sectors)
{
int err;
const uint16_t max_id = 10;
/* 50th write will trigger 1st GC. */
const uint16_t max_writes = 51;
/* 75th write will trigger 2st GC. */
const uint16_t max_writes_2 = 51 + 25;
/* 100th write will trigger 3st GC. */
const uint16_t max_writes_3 = 51 + 25 + 25;
/* 125th write will trigger 4st GC. */
const uint16_t max_writes_4 = 51 + 25 + 25 + 25;
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0,
"unexpected write sector");
/* Trigger 1st GC */
write_content(max_id, 0, max_writes, &fixture->fs);
/* sector sequence: empty,closed, write */
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2,
"unexpected write sector");
check_content(max_id, &fixture->fs);
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2,
"unexpected write sector");
check_content(max_id, &fixture->fs);
/* Trigger 2nd GC */
write_content(max_id, max_writes, max_writes_2, &fixture->fs);
/* sector sequence: write, empty, closed */
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0,
"unexpected write sector");
check_content(max_id, &fixture->fs);
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 0,
"unexpected write sector");
check_content(max_id, &fixture->fs);
/* Trigger 3rd GC */
write_content(max_id, max_writes_2, max_writes_3, &fixture->fs);
/* sector sequence: closed, write, empty */
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 1,
"unexpected write sector");
check_content(max_id, &fixture->fs);
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 1,
"unexpected write sector");
check_content(max_id, &fixture->fs);
/* Trigger 4th GC */
write_content(max_id, max_writes_3, max_writes_4, &fixture->fs);
/* sector sequence: empty,closed, write */
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2,
"unexpected write sector");
check_content(max_id, &fixture->fs);
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
zassert_equal(fixture->fs.ate_wra >> ADDR_SECT_SHIFT, 2,
"unexpected write sector");
check_content(max_id, &fixture->fs);
}
static int flash_sim_erase_calls_find(struct stats_hdr *hdr, void *arg,
const char *name, uint16_t off)
{
if (!strcmp(name, "flash_erase_calls")) {
uint32_t **flash_erase_stat = (uint32_t **) arg;
*flash_erase_stat = (uint32_t *)((uint8_t *)hdr + off);
}
return 0;
}
static int flash_sim_max_erase_calls_find(struct stats_hdr *hdr, void *arg,
const char *name, uint16_t off)
{
if (!strcmp(name, "max_erase_calls")) {
uint32_t **max_erase_calls = (uint32_t **) arg;
*max_erase_calls = (uint32_t *)((uint8_t *)hdr + off);
}
return 0;
}
static int flash_sim_max_len_find(struct stats_hdr *hdr, void *arg,
const char *name, uint16_t off)
{
if (!strcmp(name, "max_len")) {
uint32_t **max_len = (uint32_t **) arg;
*max_len = (uint32_t *)((uint8_t *)hdr + off);
}
return 0;
}
ZTEST_F(nvs, test_nvs_corrupted_sector_close_operation)
{
int err;
int len;
uint8_t buf[32];
uint32_t *flash_write_stat;
uint32_t *flash_erase_stat;
uint32_t *flash_max_write_calls;
uint32_t *flash_max_erase_calls;
uint32_t *flash_max_len;
const uint16_t max_id = 10;
/* 25th write will trigger GC. */
const uint16_t max_writes = 26;
/* Get the address of simulator parameters. */
stats_walk(fixture->sim_thresholds, flash_sim_max_write_calls_find,
&flash_max_write_calls);
stats_walk(fixture->sim_thresholds, flash_sim_max_erase_calls_find,
&flash_max_erase_calls);
stats_walk(fixture->sim_thresholds, flash_sim_max_len_find,
&flash_max_len);
stats_walk(fixture->sim_stats, flash_sim_write_calls_find, &flash_write_stat);
stats_walk(fixture->sim_stats, flash_sim_erase_calls_find, &flash_erase_stat);
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
for (uint16_t i = 0; i < max_writes; i++) {
uint8_t id = (i % max_id);
uint8_t id_data = id + max_id * (i / max_id);
memset(buf, id_data, sizeof(buf));
if (i == max_writes - 1) {
/* Reset stats. */
*flash_write_stat = 0;
*flash_erase_stat = 0;
/* Block write calls and simulate power down during
* sector closing operation, so only a part of a NVS
* closing ate will be written.
*/
*flash_max_write_calls = 1;
*flash_max_erase_calls = 1;
*flash_max_len = 4;
}
len = nvs_write(&fixture->fs, id, buf, sizeof(buf));
zassert_true(len == sizeof(buf), "nvs_write failed: %d", len);
}
/* Make the flash simulator functional again. */
*flash_max_write_calls = 0;
*flash_max_erase_calls = 0;
*flash_max_len = 0;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
check_content(max_id, &fixture->fs);
/* Ensure that the NVS is able to store new content. */
execute_long_pattern_write(max_id, &fixture->fs);
}
/**
* @brief Test case when storage become full, so only deletion is possible.
*/
ZTEST_F(nvs, test_nvs_full_sector)
{
int err;
ssize_t len;
uint16_t filling_id = 0;
uint16_t i, data_read;
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
while (1) {
len = nvs_write(&fixture->fs, filling_id, &filling_id,
sizeof(filling_id));
if (len == -ENOSPC) {
break;
}
zassert_true(len == sizeof(filling_id), "nvs_write failed: %d",
len);
filling_id++;
}
/* check whether can delete whatever from full storage */
err = nvs_delete(&fixture->fs, 1);
zassert_true(err == 0, "nvs_delete call failure: %d", err);
/* the last sector is full now, test re-initialization */
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
len = nvs_write(&fixture->fs, filling_id, &filling_id, sizeof(filling_id));
zassert_true(len == sizeof(filling_id), "nvs_write failed: %d", len);
/* sanitycheck on NVS content */
for (i = 0; i <= filling_id; i++) {
len = nvs_read(&fixture->fs, i, &data_read, sizeof(data_read));
if (i == 1) {
zassert_true(len == -ENOENT,
"nvs_read shouldn't found the entry: %d",
len);
} else {
zassert_true(len == sizeof(data_read),
"nvs_read failed: %d", i, len);
zassert_equal(data_read, i,
"read unexpected data: %d instead of %d",
data_read, i);
}
}
}
ZTEST_F(nvs, test_delete)
{
int err;
ssize_t len;
uint16_t filling_id, data_read;
uint32_t ate_wra, data_wra;
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
for (filling_id = 0; filling_id < 10; filling_id++) {
len = nvs_write(&fixture->fs, filling_id, &filling_id,
sizeof(filling_id));
zassert_true(len == sizeof(filling_id), "nvs_write failed: %d",
len);
if (filling_id != 0) {
continue;
}
/* delete the first entry while it is the most recent one */
err = nvs_delete(&fixture->fs, filling_id);
zassert_true(err == 0, "nvs_delete call failure: %d", err);
len = nvs_read(&fixture->fs, filling_id, &data_read, sizeof(data_read));
zassert_true(len == -ENOENT,
"nvs_read shouldn't found the entry: %d", len);
}
/* delete existing entry */
err = nvs_delete(&fixture->fs, 1);
zassert_true(err == 0, "nvs_delete call failure: %d", err);
len = nvs_read(&fixture->fs, 1, &data_read, sizeof(data_read));
zassert_true(len == -ENOENT, "nvs_read shouldn't found the entry: %d",
len);
ate_wra = fixture->fs.ate_wra;
data_wra = fixture->fs.data_wra;
/* delete already deleted entry */
err = nvs_delete(&fixture->fs, 1);
zassert_true(err == 0, "nvs_delete call failure: %d", err);
zassert_true(ate_wra == fixture->fs.ate_wra && data_wra == fixture->fs.data_wra,
"delete already deleted entry should not make"
" any footprint in the storage");
/* delete nonexisting entry */
err = nvs_delete(&fixture->fs, filling_id);
zassert_true(err == 0, "nvs_delete call failure: %d", err);
zassert_true(ate_wra == fixture->fs.ate_wra && data_wra == fixture->fs.data_wra,
"delete nonexistent entry should not make"
" any footprint in the storage");
}
/*
* 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.
*/
ZTEST_F(nvs, test_nvs_gc_corrupt_close_ate)
{
struct nvs_ate ate, close_ate;
uint32_t data;
ssize_t len;
int err;
close_ate.id = 0xffff;
close_ate.offset = fixture->fs.sector_size - sizeof(struct nvs_ate) * 5;
close_ate.len = 0;
close_ate.crc8 = 0xff; /* Incorrect crc8 */
ate.id = 0x1;
ate.offset = 0;
ate.len = sizeof(data);
ate.crc8 = crc8_ccitt(0xff, &ate,
offsetof(struct nvs_ate, crc8));
/* Mark sector 0 as closed */
err = flash_write(fixture->fs.flash_device, fixture->fs.offset + fixture->fs.sector_size -
sizeof(struct nvs_ate), &close_ate,
sizeof(close_ate));
zassert_true(err == 0, "flash_write failed: %d", err);
/* Write valid ate at -6 */
err = flash_write(fixture->fs.flash_device, fixture->fs.offset + fixture->fs.sector_size -
sizeof(struct nvs_ate) * 6, &ate, sizeof(ate));
zassert_true(err == 0, "flash_write failed: %d", err);
/* Write data for previous ate */
data = 0xaa55aa55;
err = flash_write(fixture->fs.flash_device, fixture->fs.offset, &data, sizeof(data));
zassert_true(err == 0, "flash_write failed: %d", err);
/* Mark sector 1 as closed */
err = flash_write(fixture->fs.flash_device,
fixture->fs.offset + (2 * fixture->fs.sector_size) -
sizeof(struct nvs_ate), &close_ate,
sizeof(close_ate));
zassert_true(err == 0, "flash_write failed: %d", err);
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
data = 0;
len = nvs_read(&fixture->fs, 1, &data, sizeof(data));
zassert_true(len == sizeof(data),
"nvs_read should have read %d bytes", sizeof(data));
zassert_true(data == 0xaa55aa55, "unexpected value %d", data);
}
/*
* Test that garbage-collection correctly handles corrupt ate's.
*/
ZTEST_F(nvs, test_nvs_gc_corrupt_ate)
{
struct nvs_ate corrupt_ate, close_ate;
int err;
close_ate.id = 0xffff;
close_ate.offset = fixture->fs.sector_size / 2;
close_ate.len = 0;
close_ate.crc8 = crc8_ccitt(0xff, &close_ate,
offsetof(struct nvs_ate, crc8));
corrupt_ate.id = 0xdead;
corrupt_ate.offset = 0;
corrupt_ate.len = 20;
corrupt_ate.crc8 = 0xff; /* Incorrect crc8 */
/* Mark sector 0 as closed */
err = flash_write(fixture->fs.flash_device, fixture->fs.offset + fixture->fs.sector_size -
sizeof(struct nvs_ate), &close_ate,
sizeof(close_ate));
zassert_true(err == 0, "flash_write failed: %d", err);
/* Write a corrupt ate */
err = flash_write(fixture->fs.flash_device,
fixture->fs.offset + (fixture->fs.sector_size / 2),
&corrupt_ate, sizeof(corrupt_ate));
zassert_true(err == 0, "flash_write failed: %d", err);
/* Mark sector 1 as closed */
err = flash_write(fixture->fs.flash_device,
fixture->fs.offset + (2 * fixture->fs.sector_size) -
sizeof(struct nvs_ate), &close_ate,
sizeof(close_ate));
zassert_true(err == 0, "flash_write failed: %d", err);
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_mount call failure: %d", err);
}
#ifdef CONFIG_NVS_LOOKUP_CACHE
static size_t num_matching_cache_entries(uint32_t addr, bool compare_sector_only, struct nvs_fs *fs)
{
size_t i, num = 0;
uint32_t mask = compare_sector_only ? ADDR_SECT_MASK : UINT32_MAX;
for (i = 0; i < CONFIG_NVS_LOOKUP_CACHE_SIZE; i++) {
if ((fs->lookup_cache[i] & mask) == addr) {
num++;
}
}
return num;
}
#endif
/*
* Test that NVS lookup cache is properly rebuilt on nvs_mount(), or initialized
* to NVS_LOOKUP_CACHE_NO_ADDR if the store is empty.
*/
ZTEST_F(nvs, test_nvs_cache_init)
{
#ifdef CONFIG_NVS_LOOKUP_CACHE
int err;
size_t num;
uint32_t ate_addr;
uint8_t data = 0;
/* Test cache initialization when the store is empty */
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_init call failure: %d", err);
num = num_matching_cache_entries(NVS_LOOKUP_CACHE_NO_ADDR, false, &fixture->fs);
zassert_equal(num, CONFIG_NVS_LOOKUP_CACHE_SIZE, "uninitialized cache");
/* Test cache update after nvs_write() */
ate_addr = fixture->fs.ate_wra;
err = nvs_write(&fixture->fs, 1, &data, sizeof(data));
zassert_equal(err, sizeof(data), "nvs_write call failure: %d", err);
num = num_matching_cache_entries(NVS_LOOKUP_CACHE_NO_ADDR, false, &fixture->fs);
zassert_equal(num, CONFIG_NVS_LOOKUP_CACHE_SIZE - 1, "cache not updated after write");
num = num_matching_cache_entries(ate_addr, false, &fixture->fs);
zassert_equal(num, 1, "invalid cache entry after write");
/* Test cache initialization when the store is non-empty */
memset(fixture->fs.lookup_cache, 0xAA, sizeof(fixture->fs.lookup_cache));
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_init call failure: %d", err);
num = num_matching_cache_entries(NVS_LOOKUP_CACHE_NO_ADDR, false, &fixture->fs);
zassert_equal(num, CONFIG_NVS_LOOKUP_CACHE_SIZE - 1, "uninitialized cache after restart");
num = num_matching_cache_entries(ate_addr, false, &fixture->fs);
zassert_equal(num, 1, "invalid cache entry after restart");
#endif
}
/*
* Test that even after writing more NVS IDs than the number of NVS lookup cache
* entries they all can be read correctly.
*/
ZTEST_F(nvs, test_nvs_cache_collission)
{
#ifdef CONFIG_NVS_LOOKUP_CACHE
int err;
uint16_t id;
uint16_t data;
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_init call failure: %d", err);
for (id = 0; id < CONFIG_NVS_LOOKUP_CACHE_SIZE + 1; id++) {
data = id;
err = nvs_write(&fixture->fs, id, &data, sizeof(data));
zassert_equal(err, sizeof(data), "nvs_write call failure: %d", err);
}
for (id = 0; id < CONFIG_NVS_LOOKUP_CACHE_SIZE + 1; id++) {
err = nvs_read(&fixture->fs, id, &data, sizeof(data));
zassert_equal(err, sizeof(data), "nvs_read call failure: %d", err);
zassert_equal(data, id, "incorrect data read");
}
#endif
}
/*
* Test that NVS lookup cache does not contain any address from gc-ed sector
*/
ZTEST_F(nvs, test_nvs_cache_gc)
{
#ifdef CONFIG_NVS_LOOKUP_CACHE
int err;
size_t num;
uint16_t data = 0;
fixture->fs.sector_count = 3;
err = nvs_mount(&fixture->fs);
zassert_true(err == 0, "nvs_init call failure: %d", err);
/* Fill the first sector with writes of ID 1 */
while (fixture->fs.data_wra + sizeof(data) <= fixture->fs.ate_wra) {
++data;
err = nvs_write(&fixture->fs, 1, &data, sizeof(data));
zassert_equal(err, sizeof(data), "nvs_write call failure: %d", err);
}
/* Verify that cache contains a single entry for sector 0 */
num = num_matching_cache_entries(0 << ADDR_SECT_SHIFT, true, &fixture->fs);
zassert_equal(num, 1, "invalid cache content after filling sector 0");
/* Fill the second sector with writes of ID 2 */
while ((fixture->fs.ate_wra >> ADDR_SECT_SHIFT) != 2) {
++data;
err = nvs_write(&fixture->fs, 2, &data, sizeof(data));
zassert_equal(err, sizeof(data), "nvs_write call failure: %d", err);
}
/*
* At this point sector 0 should have been gc-ed. Verify that action is
* reflected by the cache content.
*/
num = num_matching_cache_entries(0 << ADDR_SECT_SHIFT, true, &fixture->fs);
zassert_equal(num, 0, "not invalidated cache entries aftetr gc");
num = num_matching_cache_entries(2 << ADDR_SECT_SHIFT, true, &fixture->fs);
zassert_equal(num, 2, "invalid cache content after gc");
#endif
}