fork the esp-idf fatfs for f_forward and exfat support

This commit is contained in:
jacqueline
2023-07-25 17:42:00 +10:00
parent 9287c4eb8c
commit 7b72e5479e
86 changed files with 34272 additions and 0 deletions
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.16)
set(COMPONENTS main)
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../test_fatfs_common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_fatfs_flash_ro)
+14
View File
@@ -0,0 +1,14 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
This test app runs a few FATFS test cases in a read-only FAT partition.
These tests should be possible to run on any ESP development board, not extra hardware is necessary.
The initial FAT image is generated during the build process in [main/CMakeLists.txt](main/CMakeLists.txt):
- `create_test_files` function creates a set of files expected by the test cases
- `fatfs_create_rawflash_image` generates a FAT image from the set of files (via `fatfsgen.py`)
The generated FAT image is flashed into `storage` partition when running `idf.py flash`.
See [../README.md](../README.md) for more information about FATFS test apps.
@@ -0,0 +1,41 @@
idf_component_register(SRCS "test_fatfs_flash_ro.c"
INCLUDE_DIRS "."
PRIV_REQUIRES unity spi_flash fatfs vfs test_fatfs_common
WHOLE_ARCHIVE)
set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/fatfs_image")
# This helper function creates a set of files expected by the test case.
# The files are then added into the FAT image by 'fatfs_create_rawflash_image' below.
function(create_test_files)
message(STATUS "Generating source files for test_fatfs_flash_ro in ${out_dir}...")
# used in "(raw) can read file"
file(WRITE "${out_dir}/hello.txt" "Hello, World!\n")
# used in "(raw) can open maximum number of files"
foreach(i RANGE 1 32)
file(WRITE "${out_dir}/f/${i}.txt")
endforeach()
# used in "(raw) opendir, readdir, rewinddir, seekdir work as expected"
file(WRITE "${out_dir}/dir/1.txt")
file(WRITE "${out_dir}/dir/2.txt")
file(WRITE "${out_dir}/dir/boo.bin")
file(WRITE "${out_dir}/dir/inner/3.txt")
# used in "(raw) multiple tasks can use same volume"
foreach(i RANGE 1 4)
string(REPEAT "${i}" 32000 file_content)
file(WRITE "${out_dir}/ccrnt/${i}.txt" ${file_content})
endforeach()
# used in "(raw) read speed test"
string(REPEAT "a" 262144 file_content)
file(WRITE "${out_dir}/256k.bin" ${file_content})
endfunction()
create_test_files()
fatfs_create_rawflash_image(storage ${out_dir} FLASH_IN_PROJECT PRESERVE_TIME)
@@ -0,0 +1,311 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include "unity.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "test_fatfs_common.h"
void app_main(void)
{
unity_run_menu();
}
static void test_setup(size_t max_files)
{
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = max_files
};
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_ro("/spiflash", "storage", &mount_config));
}
static void test_teardown(void)
{
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_ro("/spiflash", "storage"));
}
TEST_CASE("(raw) can read file", "[fatfs]")
{
test_setup(5);
FILE* f = fopen("/spiflash/hello.txt", "r");
TEST_ASSERT_NOT_NULL(f);
char buf[32] = { 0 };
int cb = fread(buf, 1, sizeof(buf), f);
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), cb);
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
TEST_ASSERT_EQUAL(0, fclose(f));
test_teardown();
}
TEST_CASE("(raw) can open maximum number of files", "[fatfs]")
{
size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */
test_setup(max_files);
FILE** files = calloc(max_files, sizeof(FILE*));
for (size_t i = 0; i < max_files; ++i) {
char name[32];
snprintf(name, sizeof(name), "/spiflash/f/%d.txt", i + 1);
files[i] = fopen(name, "r");
TEST_ASSERT_NOT_NULL(files[i]);
}
/* close everything and clean up */
for (size_t i = 0; i < max_files; ++i) {
fclose(files[i]);
}
free(files);
test_teardown();
}
TEST_CASE("(raw) can lseek", "[fatfs]")
{
test_setup(5);
FILE* f = fopen("/spiflash/hello.txt", "r");
TEST_ASSERT_NOT_NULL(f);
TEST_ASSERT_EQUAL(0, fseek(f, 2, SEEK_CUR));
TEST_ASSERT_EQUAL('l', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 4, SEEK_SET));
TEST_ASSERT_EQUAL('o', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, -5, SEEK_END));
TEST_ASSERT_EQUAL('r', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END));
TEST_ASSERT_EQUAL(17, ftell(f));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
TEST_ASSERT_EQUAL(14, ftell(f));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET));
test_teardown();
}
TEST_CASE("(raw) stat returns correct values", "[fatfs]")
{
test_setup(5);
struct tm tm;
tm.tm_year = 2018 - 1900;
tm.tm_mon = 5; // Note: month can be 0-11 & not 1-12
tm.tm_mday = 13;
tm.tm_hour = 11;
tm.tm_min = 2;
tm.tm_sec = 10;
time_t t = mktime(&tm);
printf("Reference time: %s", asctime(&tm));
struct stat st;
TEST_ASSERT_EQUAL(0, stat("/spiflash/hello.txt", &st));
time_t mtime = st.st_mtime;
struct tm mtm;
localtime_r(&mtime, &mtm);
printf("File time: %s", asctime(&mtm));
TEST_ASSERT(mtime > t); // Modification time should be in future wrt ref time
TEST_ASSERT(st.st_mode & S_IFREG);
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
memset(&st, 0, sizeof(st));
TEST_ASSERT_EQUAL(0, stat("/spiflash", &st));
TEST_ASSERT(st.st_mode & S_IFDIR);
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
test_teardown();
}
TEST_CASE("(raw) can opendir root directory of FS", "[fatfs]")
{
test_setup(5);
DIR* dir = opendir("/spiflash");
TEST_ASSERT_NOT_NULL(dir);
bool found = false;
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
if (strcasecmp(de->d_name, "hello.txt") == 0) {
found = true;
break;
}
}
TEST_ASSERT_TRUE(found);
TEST_ASSERT_EQUAL(0, closedir(dir));
test_teardown();
}
TEST_CASE("(raw) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]")
{
test_setup(5);
DIR* dir = opendir("/spiflash/dir");
TEST_ASSERT_NOT_NULL(dir);
int count = 0;
const char* names[4];
while(count < 4) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
printf("found '%s'\n", de->d_name);
if (strcasecmp(de->d_name, "1.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "1.txt";
++count;
} else if (strcasecmp(de->d_name, "2.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "2.txt";
++count;
} else if (strcasecmp(de->d_name, "inner") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_DIR);
names[count] = "inner";
++count;
} else if (strcasecmp(de->d_name, "boo.bin") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "boo.bin";
++count;
} else {
TEST_FAIL_MESSAGE("unexpected directory entry");
}
}
TEST_ASSERT_EQUAL(count, 4);
rewinddir(dir);
struct dirent* de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0]));
seekdir(dir, 3);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3]));
seekdir(dir, 1);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1]));
seekdir(dir, 2);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2]));
TEST_ASSERT_EQUAL(0, closedir(dir));
test_teardown();
}
typedef struct {
const char* filename;
size_t word_count;
unsigned val;
SemaphoreHandle_t done;
esp_err_t result;
} read_test_arg_t;
#define READ_TEST_ARG_INIT(name, val_) \
{ \
.filename = name, \
.word_count = 8000, \
.val = val_, \
.done = xSemaphoreCreateBinary() \
}
static void read_task(void* param)
{
read_test_arg_t* args = (read_test_arg_t*) param;
FILE* f = fopen(args->filename, "rb");
if (f == NULL) {
args->result = ESP_ERR_NOT_FOUND;
goto done;
}
for (size_t i = 0; i < args->word_count; ++i) {
unsigned rval;
int cnt = fread(&rval, sizeof(rval), 1, f);
if (cnt != 1 || rval != args->val) {
printf("E(r): i=%d, cnt=%d rval=0x08%x val=0x%08x\n", i, cnt, rval, args->val);
args->result = ESP_FAIL;
goto close;
}
}
args->result = ESP_OK;
close:
fclose(f);
done:
xSemaphoreGive(args->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
TEST_CASE("(raw) multiple tasks can use same volume", "[fatfs]")
{
test_setup(5);
char names[4][64];
for (size_t i = 0; i < 4; ++i) {
snprintf(names[i], sizeof(names[i]), "/spiflash/ccrnt/%d.txt", i + 1);
}
read_test_arg_t args1 = READ_TEST_ARG_INIT(names[0], 0x31313131);
read_test_arg_t args2 = READ_TEST_ARG_INIT(names[1], 0x32323232);
read_test_arg_t args3 = READ_TEST_ARG_INIT(names[2], 0x33333333);
read_test_arg_t args4 = READ_TEST_ARG_INIT(names[3], 0x34343434);
const int cpuid_0 = 0;
const int cpuid_1 = portNUM_PROCESSORS - 1;
const int stack_size = 4096;
printf("reading files 1.txt 2.txt 3.txt 4.txt \n");
xTaskCreatePinnedToCore(&read_task, "r1", stack_size, &args1, 3, NULL, cpuid_1);
xTaskCreatePinnedToCore(&read_task, "r2", stack_size, &args2, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_task, "r3", stack_size, &args3, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_task, "r4", stack_size, &args4, 3, NULL, cpuid_1);
xSemaphoreTake(args1.done, portMAX_DELAY);
printf("1.txt done\n");
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
xSemaphoreTake(args2.done, portMAX_DELAY);
printf("2.txt done\n");
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
xSemaphoreTake(args3.done, portMAX_DELAY);
printf("3.txt done\n");
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
xSemaphoreTake(args4.done, portMAX_DELAY);
printf("4.txt done\n");
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
vSemaphoreDelete(args1.done);
vSemaphoreDelete(args2.done);
vSemaphoreDelete(args3.done);
vSemaphoreDelete(args4.done);
test_teardown();
}
TEST_CASE("(raw) read speed test", "[fatfs][timeout=60]")
{
test_setup(5);
const size_t buf_size = 16 * 1024;
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
const size_t file_size = 256 * 1024;
const char* file = "/spiflash/256k.bin";
test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, false);
test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, false);
test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, false);
free(buf);
test_teardown();
}
@@ -0,0 +1,3 @@
# Name, Type, SubType, Offset, Size, Flags
factory, app, factory, 0x10000, 1M,
storage, data, fat, , 528k,
1 # Name Type SubType Offset Size Flags
2 factory app factory 0x10000 1M
3 storage data fat 528k
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.supported_targets
@pytest.mark.generic
def test_fatfs_flash_ro(dut: Dut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('')
dut.expect_exact('Enter test for running.')
dut.write('*')
dut.expect_unity_test_output()
@@ -0,0 +1,14 @@
# General options for additional checks
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
# disable task watchdog since this app uses an interactive menu
CONFIG_ESP_TASK_WDT_INIT=n
# use custom partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"