mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
5962a7e931
* Migrate test cases from IDF test runner to Unity fixture. * Tighten heap checks in the test case a bit. * Run formatting script on test_wl.c * NEW: Define 4 test configurations, including configs with 512 byte sector size. Previously these configs weren't tested in CI. * NEW: The test app only runs for ESP32 and ESP32-C3 (one chip for each architecture). The component is pretty high level so we don't need to test it for each chip. This reduces the load on CI.
336 lines
11 KiB
C
336 lines
11 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <string.h>
|
|
#include "unity.h"
|
|
#include "unity_fixture.h"
|
|
#include "wear_levelling.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
#include "esp_private/esp_clk.h"
|
|
#include "soc/cpu.h"
|
|
#include "sdkconfig.h"
|
|
|
|
|
|
TEST_GROUP(wear_levelling);
|
|
|
|
TEST_SETUP(wear_levelling)
|
|
{
|
|
}
|
|
|
|
TEST_TEAR_DOWN(wear_levelling)
|
|
{
|
|
}
|
|
|
|
static const esp_partition_t *get_test_data_partition(void)
|
|
{
|
|
const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
|
ESP_PARTITION_SUBTYPE_ANY, "flash_test");
|
|
TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */
|
|
return result;
|
|
}
|
|
|
|
TEST(wear_levelling, wl_unmount_doesnt_leak_memory)
|
|
{
|
|
const esp_partition_t *partition = get_test_data_partition();
|
|
wl_handle_t handle;
|
|
// mount and unmount once to initialize static locks
|
|
TEST_ESP_OK(wl_mount(partition, &handle));
|
|
wl_unmount(handle);
|
|
|
|
// test that we didn't leak any memory on the next init/deinit
|
|
size_t size_before = xPortGetFreeHeapSize();
|
|
TEST_ESP_OK(wl_mount(partition, &handle));
|
|
wl_unmount(handle);
|
|
size_t size_after = xPortGetFreeHeapSize();
|
|
|
|
TEST_ASSERT_EQUAL(size_before, size_after);
|
|
}
|
|
|
|
TEST(wear_levelling, wl_mount_checks_partition_params)
|
|
{
|
|
const esp_partition_t *test_partition = get_test_data_partition();
|
|
esp_partition_t fake_partition;
|
|
memcpy(&fake_partition, test_partition, sizeof(fake_partition));
|
|
wl_handle_t handle;
|
|
size_t size_before, size_after;
|
|
wl_unmount(WL_INVALID_HANDLE);
|
|
|
|
esp_partition_erase_range(test_partition, 0, test_partition->size);
|
|
// test small partition: result should be error
|
|
for (int i = 0; i < 5; i++) {
|
|
fake_partition.size = SPI_FLASH_SEC_SIZE * (i);
|
|
size_before = xPortGetFreeHeapSize();
|
|
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, wl_mount(&fake_partition, &handle));
|
|
// test that we didn't leak any memory
|
|
size_after = xPortGetFreeHeapSize();
|
|
TEST_ASSERT_EQUAL_HEX32(size_before, size_after);
|
|
}
|
|
|
|
// test minimum size partition: result should be OK
|
|
fake_partition.size = SPI_FLASH_SEC_SIZE * 5;
|
|
size_before = xPortGetFreeHeapSize();
|
|
TEST_ESP_OK(wl_mount(&fake_partition, &handle));
|
|
wl_unmount(handle);
|
|
|
|
// test that we didn't leak any memory
|
|
size_after = xPortGetFreeHeapSize();
|
|
TEST_ASSERT_EQUAL_HEX32(size_before, size_after);
|
|
}
|
|
|
|
typedef struct {
|
|
size_t offset;
|
|
bool write;
|
|
size_t word_count;
|
|
int seed;
|
|
SemaphoreHandle_t done;
|
|
int result;
|
|
wl_handle_t handle;
|
|
} read_write_test_arg_t;
|
|
|
|
#define READ_WRITE_TEST_ARG_INIT(offset_, seed_, handle_, count_) \
|
|
{ \
|
|
.offset = offset_, \
|
|
.seed = seed_, \
|
|
.word_count = count_, \
|
|
.write = true, \
|
|
.done = xSemaphoreCreateBinary(), \
|
|
.handle = handle_ \
|
|
}
|
|
|
|
static void read_write_task(void *param)
|
|
{
|
|
read_write_test_arg_t *args = (read_write_test_arg_t *) param;
|
|
esp_err_t err;
|
|
srand(args->seed);
|
|
for (size_t i = 0; i < args->word_count; ++i) {
|
|
uint32_t val = rand();
|
|
if (args->write) {
|
|
err = wl_write(args->handle, args->offset + i * sizeof(val), &val, sizeof(val));
|
|
if (err != ESP_OK) {
|
|
args->result = err;
|
|
goto done;
|
|
}
|
|
} else {
|
|
uint32_t rval;
|
|
err = wl_read(args->handle, args->offset + i * sizeof(rval), &rval, sizeof(rval));
|
|
if (err != ESP_OK || rval != val) {
|
|
printf("E: i=%d, cnt=%d rval=%d val=%d\n\n", i, args->word_count, rval, val);
|
|
args->result = ESP_FAIL;
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
args->result = ESP_OK;
|
|
|
|
done:
|
|
xSemaphoreGive(args->done);
|
|
vTaskDelay(1);
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
TEST(wear_levelling, multiple_tasks_single_handle)
|
|
{
|
|
const esp_partition_t *partition = get_test_data_partition();
|
|
wl_handle_t handle;
|
|
TEST_ESP_OK(wl_mount(partition, &handle));
|
|
|
|
size_t sector_size = wl_sector_size(handle);
|
|
TEST_ESP_OK(wl_erase_range(handle, 0, sector_size * 8));
|
|
read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(0, 1, handle, sector_size / sizeof(uint32_t));
|
|
read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(sector_size, 2, handle, sector_size / sizeof(uint32_t));
|
|
const size_t stack_size = 8192;
|
|
|
|
printf("writing 1 and 2\n");
|
|
const int cpuid_0 = 0;
|
|
const int cpuid_1 = portNUM_PROCESSORS - 1;
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_1);
|
|
|
|
xSemaphoreTake(args1.done, portMAX_DELAY);
|
|
printf("f1 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
|
|
xSemaphoreTake(args2.done, portMAX_DELAY);
|
|
printf("f2 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
|
|
|
|
args1.write = false;
|
|
args2.write = false;
|
|
read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT(2 * sector_size, 3, handle, sector_size / sizeof(uint32_t));
|
|
read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(3 * sector_size, 4, handle, sector_size / sizeof(uint32_t));
|
|
|
|
printf("reading 1 and 2, writing 3 and 4\n");
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw3", stack_size, &args3, 3, NULL, cpuid_1);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw4", stack_size, &args4, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_1);
|
|
|
|
xSemaphoreTake(args1.done, portMAX_DELAY);
|
|
printf("f1 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
|
|
xSemaphoreTake(args2.done, portMAX_DELAY);
|
|
printf("f2 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
|
|
xSemaphoreTake(args3.done, portMAX_DELAY);
|
|
printf("f3 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
|
|
xSemaphoreTake(args4.done, portMAX_DELAY);
|
|
printf("f4 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
|
|
|
|
vSemaphoreDelete(args1.done);
|
|
vSemaphoreDelete(args2.done);
|
|
vSemaphoreDelete(args3.done);
|
|
vSemaphoreDelete(args4.done);
|
|
wl_unmount(handle);
|
|
}
|
|
|
|
#define TEST_SECTORS_COUNT 8
|
|
|
|
static void check_mem_data(wl_handle_t handle, uint32_t init_val, uint32_t *buff)
|
|
{
|
|
size_t sector_size = wl_sector_size(handle);
|
|
|
|
for (int m = 0; m < TEST_SECTORS_COUNT; m++) {
|
|
TEST_ESP_OK(wl_read(handle, sector_size * m, buff, sector_size));
|
|
for (int i = 0; i < sector_size / sizeof(uint32_t); i++) {
|
|
uint32_t compare_val = init_val + i + m * sector_size;
|
|
TEST_ASSERT_EQUAL( buff[i], compare_val);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// We write complete memory with defined data
|
|
// And then write one sector many times.
|
|
// A data in other secors should be the same.
|
|
// We do this also with unmount
|
|
TEST(wear_levelling, write_doesnt_touch_other_sectors)
|
|
{
|
|
const esp_partition_t *partition = get_test_data_partition();
|
|
esp_partition_t fake_partition;
|
|
memcpy(&fake_partition, partition, sizeof(fake_partition));
|
|
|
|
fake_partition.size = SPI_FLASH_SEC_SIZE * (4 + TEST_SECTORS_COUNT);
|
|
|
|
wl_handle_t handle;
|
|
TEST_ESP_OK(wl_mount(&fake_partition, &handle));
|
|
|
|
size_t sector_size = wl_sector_size(handle);
|
|
// Erase 8 sectors
|
|
TEST_ESP_OK(wl_erase_range(handle, 0, sector_size * TEST_SECTORS_COUNT));
|
|
// Write data to all sectors
|
|
printf("Check 1 sector_size=0x%08x\n", sector_size);
|
|
// Set initial random value
|
|
uint32_t init_val = rand();
|
|
|
|
uint32_t *buff = (uint32_t *)malloc(sector_size);
|
|
for (int m = 0; m < TEST_SECTORS_COUNT; m++) {
|
|
for (int i = 0; i < sector_size / sizeof(uint32_t); i++) {
|
|
buff[i] = init_val + i + m * sector_size;
|
|
}
|
|
TEST_ESP_OK(wl_erase_range(handle, sector_size * m, sector_size));
|
|
TEST_ESP_OK(wl_write(handle, sector_size * m, buff, sector_size));
|
|
}
|
|
|
|
check_mem_data(handle, init_val, buff);
|
|
|
|
uint32_t start;
|
|
start = cpu_hal_get_cycle_count();
|
|
|
|
|
|
for (int m = 0; m < 100000; m++) {
|
|
uint32_t sector = m % TEST_SECTORS_COUNT;
|
|
for (int i = 0; i < sector_size / sizeof(uint32_t); i++) {
|
|
buff[i] = init_val + i + sector * sector_size;
|
|
}
|
|
TEST_ESP_OK(wl_erase_range(handle, sector_size * sector, sector_size));
|
|
TEST_ESP_OK(wl_write(handle, sector_size * sector, buff, sector_size));
|
|
check_mem_data(handle, init_val, buff);
|
|
|
|
uint32_t end;
|
|
end = cpu_hal_get_cycle_count();
|
|
uint32_t ms = (end - start) / (esp_clk_cpu_freq() / 1000);
|
|
printf("loop %4i pass, time= %ims\n", m, ms);
|
|
if (ms > 10000) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buff);
|
|
wl_unmount(handle);
|
|
}
|
|
|
|
|
|
#if CONFIG_WL_SECTOR_SIZE_4096
|
|
// This test runs for 4k sector size only, since the original (version 1) partition binary is generated this way
|
|
extern const uint8_t test_partition_v1_bin_start[] asm("_binary_test_partition_v1_bin_start");
|
|
extern const uint8_t test_partition_v1_bin_end[] asm("_binary_test_partition_v1_bin_end");
|
|
|
|
#define COMPARE_START_CONST 0x12340000
|
|
|
|
// We write to partition prepared image with V1
|
|
// Then we convert image to new version and verifying the data
|
|
TEST(wear_levelling, version_update)
|
|
{
|
|
const esp_partition_t *partition = get_test_data_partition();
|
|
esp_partition_t fake_partition;
|
|
memcpy(&fake_partition, partition, sizeof(fake_partition));
|
|
|
|
if (partition->encrypted) {
|
|
printf("Update from V1 to V2 will not work.\n");
|
|
return;
|
|
}
|
|
fake_partition.size = (size_t)(test_partition_v1_bin_end - test_partition_v1_bin_start);
|
|
|
|
printf("Data file size = %i, partition address = 0x%08x, file addr=0x%08x\n", (uint32_t)fake_partition.size, (uint32_t)fake_partition.address, (uint32_t)test_partition_v1_bin_start);
|
|
|
|
esp_partition_erase_range(&fake_partition, 0, fake_partition.size);
|
|
|
|
esp_partition_write(&fake_partition, 0, test_partition_v1_bin_start, fake_partition.size);
|
|
for (int i = 0; i < 3; i++) {
|
|
printf("Pass %i\n", i);
|
|
wl_handle_t handle;
|
|
TEST_ESP_OK(wl_mount(&fake_partition, &handle));
|
|
size_t sector_size = wl_sector_size(handle);
|
|
uint32_t *buff = (uint32_t *)malloc(sector_size);
|
|
|
|
uint32_t init_val = COMPARE_START_CONST;
|
|
int test_count = fake_partition.size / sector_size - 4;
|
|
|
|
for (int m = 0; m < test_count; m++) {
|
|
TEST_ESP_OK(wl_read(handle, sector_size * m, buff, sector_size));
|
|
for (int i = 0; i < sector_size / sizeof(uint32_t); i++) {
|
|
uint32_t compare_val = init_val + i + m * sector_size;
|
|
if (buff[i] != compare_val) {
|
|
printf("error compare: 0x%08x != 0x%08x \n", buff[i], compare_val);
|
|
}
|
|
TEST_ASSERT_EQUAL( buff[i], compare_val);
|
|
}
|
|
}
|
|
free(buff);
|
|
wl_unmount(handle);
|
|
}
|
|
}
|
|
#endif // CONFIG_WL_SECTOR_SIZE_4096
|
|
|
|
TEST_GROUP_RUNNER(wear_levelling)
|
|
{
|
|
RUN_TEST_CASE(wear_levelling, wl_unmount_doesnt_leak_memory)
|
|
RUN_TEST_CASE(wear_levelling, wl_mount_checks_partition_params)
|
|
RUN_TEST_CASE(wear_levelling, multiple_tasks_single_handle)
|
|
RUN_TEST_CASE(wear_levelling, write_doesnt_touch_other_sectors)
|
|
|
|
#if CONFIG_WL_SECTOR_SIZE_4096
|
|
RUN_TEST_CASE(wear_levelling, version_update)
|
|
#endif
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
UNITY_MAIN(wear_levelling);
|
|
}
|