FATFS support without wear levelling

This change allows readonly FATFS to be mounted without wear levelling
support. This will provide the customers a simple way to mount FATFS images
generated on host and flashed onto the chip during factory provisioning.
Since NVS encryption is not supported yet and NVS entry size is limited,
the change will provide an easy alternative for securing the provisioning data
by just marking FATFS parition as encrypted.
This commit is contained in:
Sagar Bijwe 2018-06-08 17:39:00 +05:30
parent ac8bd0d637
commit fd7b794e81
16 changed files with 636 additions and 20 deletions

View file

@ -881,6 +881,12 @@ UT_001_30:
- ESP32_IDF
- UT_T1_1
UT_001_31:
<<: *unit_test_template
tags:
- ESP32_IDF
- UT_T1_1
UT_002_01:
<<: *unit_test_template
tags:

View file

@ -0,0 +1,104 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#include <string.h>
#include "diskio.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_log.h"
#include "diskio_rawflash.h"
static const char* TAG = "diskio_rawflash";
const esp_partition_t* ff_raw_handles[FF_VOLUMES];
DSTATUS ff_raw_initialize (BYTE pdrv)
{
return 0;
}
DSTATUS ff_raw_status (BYTE pdrv)
{
return 0;
}
DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
ESP_LOGV(TAG, "ff_raw_read - pdrv=%i, sector=%i, count=%in", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
const esp_partition_t* part = ff_raw_handles[pdrv];
assert(part);
esp_err_t err = esp_partition_read(part, sector * SPI_FLASH_SEC_SIZE, buff, count * SPI_FLASH_SEC_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_partition_read failed (0x%x)", err);
return RES_ERROR;
}
return RES_OK;
}
DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
return RES_ERROR;
}
DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff)
{
const esp_partition_t* part = ff_raw_handles[pdrv];
ESP_LOGV(TAG, "ff_raw_ioctl: cmd=%in", cmd);
assert(part);
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*((DWORD *) buff) = part->size / SPI_FLASH_SEC_SIZE;
return RES_OK;
case GET_SECTOR_SIZE:
*((WORD *) buff) = SPI_FLASH_SEC_SIZE;
return RES_OK;
case GET_BLOCK_SIZE:
return RES_ERROR;
}
return RES_ERROR;
}
esp_err_t ff_diskio_register_raw_partition(BYTE pdrv, const esp_partition_t* part_handle)
{
if (pdrv >= FF_VOLUMES) {
return ESP_ERR_INVALID_ARG;
}
static const ff_diskio_impl_t raw_impl = {
.init = &ff_raw_initialize,
.status = &ff_raw_status,
.read = &ff_raw_read,
.write = &ff_raw_write,
.ioctl = &ff_raw_ioctl
};
ff_diskio_register(pdrv, &raw_impl);
ff_raw_handles[pdrv] = part_handle;
return ESP_OK;
}
BYTE ff_diskio_get_pdrv_raw(const esp_partition_t* part_handle)
{
for (int i = 0; i < FF_VOLUMES; i++) {
if (part_handle == ff_raw_handles[i]) {
return i;
}
}
return 0xff;
}

View file

@ -0,0 +1,38 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#ifndef _DISKIO_RAWFLASH_DEFINED
#define _DISKIO_RAWFLASH_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h"
#include "esp_partition.h"
/**
* Register spi flash partition
*
* @param pdrv drive number
* @param part_handle pointer to raw flash partition.
*/
esp_err_t ff_diskio_register_raw_partition(BYTE pdrv, const esp_partition_t* part_handle);
BYTE ff_diskio_get_pdrv_raw(const esp_partition_t* part_handle);
#ifdef __cplusplus
}
#endif
#endif // _DISKIO_RAWFLASH_DEFINED

View file

@ -17,7 +17,7 @@
#include "ffconf.h"
#include "ff.h"
#include "esp_log.h"
#include "diskio_spiflash.h"
#include "diskio_wl.h"
#include "wear_levelling.h"
static const char* TAG = "ff_diskio_spiflash";

View file

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _DISKIO_SPIFLASH_DEFINED
#define _DISKIO_SPIFLASH_DEFINED
#ifndef _DISKIO_WL_DEFINED
#define _DISKIO_WL_DEFINED
#ifdef __cplusplus
extern "C" {
@ -36,4 +36,4 @@ BYTE ff_diskio_get_pdrv_wl(wl_handle_t flash_handle);
}
#endif
#endif // _DISKIO_SPIFLASH_DEFINED
#endif // _DISKIO_WL_DEFINED

View file

@ -205,6 +205,47 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
*/
esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle);
/**
* @brief Convenience function to initialize read-only FAT filesystem and register it in VFS
*
* This is an all-in-one function which does the following:
*
* - finds the partition with defined partition_label. Partition label should be
* configured in the partition table.
* - mounts FAT partition using FATFS library
* - registers FATFS library with VFS, with prefix given by base_prefix variable
*
* @note Wear levelling is not used when FAT is mounted in read-only mode using this function.
*
* @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
* @param partition_label label of the partition which should be used
* @param mount_config pointer to structure with extra parameters for mounting FATFS
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_rawflash_mount was already called for the same partition
* - ESP_ERR_NO_MEM if memory can not be allocated
* - ESP_FAIL if partition can not be mounted
* - other error codes from SPI flash driver, or FATFS drivers
*/
esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
const char* partition_label,
const esp_vfs_fat_mount_config_t* mount_config);
/**
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_rawflash_mount
*
* @param base_path path where partition should be registered (e.g. "/spiflash")
* @param partition_label label of partition to be unmounted
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount hasn't been called
*/
esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label);
#ifdef __cplusplus
}
#endif

View file

@ -20,8 +20,10 @@
#include "vfs_fat_internal.h"
#include "diskio.h"
#include "diskio_rawflash.h"
#include "wear_levelling.h"
#include "diskio_spiflash.h"
#include "diskio_wl.h"
static const char *TAG = "vfs_fat_spiflash";
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
@ -128,3 +130,78 @@ esp_err_t esp_vfs_fat_spiflash_unmount(const char *base_path, wl_handle_t wl_han
if (err == ESP_OK) err = err_drv;
return err;
}
esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
const char* partition_label,
const esp_vfs_fat_mount_config_t* mount_config)
{
esp_err_t result = ESP_OK;
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
if (data_partition == NULL) {
ESP_LOGE(TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
return ESP_ERR_NOT_FOUND;
}
// connect driver to FATFS
BYTE pdrv = 0xFF;
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
char drv[3] = {(char)('0' + pdrv), ':', 0};
result = ff_diskio_register_raw_partition(pdrv, data_partition);
if (result != ESP_OK) {
ESP_LOGE(TAG, "ff_diskio_register_raw_partition failed pdrv=%i, error - 0x(%x)", pdrv, result);
goto fail;
}
FATFS *fs;
result = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
if (result == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (result != ESP_OK) {
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", result);
goto fail;
}
// Try to mount partition
FRESULT fresult = f_mount(fs, drv, 1);
if (fresult != FR_OK) {
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
result = ESP_FAIL;
goto fail;
}
return ESP_OK;
fail:
esp_vfs_fat_unregister_path(base_path);
ff_diskio_unregister(pdrv);
return result;
}
esp_err_t esp_vfs_fat_rawflash_unmount(const char *base_path, const char* partition_label)
{
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
if (data_partition == NULL) {
ESP_LOGE(TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
return ESP_ERR_NOT_FOUND;
}
BYTE pdrv = ff_diskio_get_pdrv_raw(data_partition);
if (pdrv == 0xff) {
return ESP_ERR_INVALID_STATE;
}
char drv[3] = {(char)('0' + pdrv), ':', 0};
f_mount(0, drv, 0);
ff_diskio_unregister(pdrv);
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
return err;
}

View file

@ -1 +1,2 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
COMPONENT_EMBED_TXTFILES := fatfs.img

Binary file not shown.

View file

@ -30,7 +30,6 @@
} \
} while(0)
const char* fatfs_test_hello_str;
const char* fatfs_test_hello_str_utf;

View file

@ -0,0 +1,341 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include "unity.h"
#include "test_utils.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "test_fatfs_common.h"
#include "esp_partition.h"
#include "ff.h"
static void test_setup(size_t max_files)
{
extern const char fatfs_start[] asm("_binary_fatfs_img_start");
extern const char fatfs_end[] asm("_binary_fatfs_img_end");
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = max_files
};
const esp_partition_t* part = get_test_data_partition();
TEST_ASSERT(part->size == (fatfs_end - fatfs_start - 1));
esp_partition_erase_range(part, 0, part->size);
for (int i = 0; i < part->size; i+= SPI_FLASH_SEC_SIZE) {
ESP_ERROR_CHECK( esp_partition_write(part, i, fatfs_start + i, SPI_FLASH_SEC_SIZE) );
}
TEST_ESP_OK(esp_vfs_fat_rawflash_mount("/spiflash", "flash_test", &mount_config));
}
static void test_teardown()
{
TEST_ESP_OK(esp_vfs_fat_rawflash_unmount("/spiflash","flash_test"));
}
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/stat.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, "test_opd.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;
int seed;
int val;
SemaphoreHandle_t done;
int result;
} read_test_arg_t;
#define READ_TEST_ARG_INIT(name, seed_, val_) \
{ \
.filename = name, \
.seed = seed_, \
.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;
}
srand(args->seed);
for (size_t i = 0; i < args->word_count; ++i) {
uint32_t rval;
int cnt = fread(&rval, sizeof(rval), 1, f);
if (cnt != 1 || rval != args->val) {
ets_printf("E(r): i=%d, cnt=%d rval=%d val=%d\n\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], 1, 0x31313131);
read_test_arg_t args2 = READ_TEST_ARG_INIT(names[1], 2, 0x32323232);
read_test_arg_t args3 = READ_TEST_ARG_INIT(names[2], 3, 0x33333333);
read_test_arg_t args4 = READ_TEST_ARG_INIT(names[3], 4, 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) write/read speed test", "[fatfs][timeout=60]")
{
/* Erase partition before running the test to get consistent results */
const esp_partition_t* part = get_test_data_partition();
esp_partition_erase_range(part, 0, part->size);
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();
}

View file

@ -24,7 +24,7 @@ SOURCE_FILES = \
ff.c \
ffsystem.c \
ffunicode.c \
diskio_spiflash.c \
diskio_wl.c \
) \
$(addprefix ./stubs/, log/log.c)

View file

@ -5,7 +5,7 @@
#include "esp_partition.h"
#include "wear_levelling.h"
#include "diskio.h"
#include "diskio_spiflash.h"
#include "diskio_wl.h"
#include "catch.hpp"

View file

@ -8,27 +8,27 @@ Additionally, FatFs has been modified to support run-time pluggable disk IO laye
Using FatFs with VFS
--------------------
``esp_vfs_fat.h`` header file defines functions to connect FatFs with VFS. ``esp_vfs_fat_register`` function allocates a ``FATFS`` structure, and registers a given path prefix in VFS. Subsequent operations on files starting with this prefix are forwarded to FatFs APIs. ``esp_vfs_fat_unregister_path`` function deletes the registration with VFS, and frees the ``FATFS`` structure.
:component_file:`fatfs/src/esp_vfs_fat.h` header file defines functions to connect FatFs with VFS. :cpp:func:`esp_vfs_fat_register` function allocates a ``FATFS`` structure, and registers a given path prefix in VFS. Subsequent operations on files starting with this prefix are forwarded to FatFs APIs. :cpp:func:`esp_vfs_fat_unregister_path` function deletes the registration with VFS, and frees the ``FATFS`` structure.
Most applications will use the following flow when working with ``esp_vfs_fat_`` functions:
1. Call ``esp_vfs_fat_register``, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``, ``"/spiflash"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure.
1. Call :cpp:func:`esp_vfs_fat_register`, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``, ``"/spiflash"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure.
2. Call ``ff_diskio_register`` function to register disk IO driver for the drive number used in step 1.
2. Call :cpp:func:`ff_diskio_register` function to register disk IO driver for the drive number used in step 1.
3. Call ``f_mount`` function (and optionally ``f_fdisk``, ``f_mkfs``) to mount the filesystem using the same drive number which was passed to ``esp_vfs_fat_register``. See FatFs documentation for more details.
3. Call FatFs ``f_mount`` function (and optionally ``f_fdisk``, ``f_mkfs``) to mount the filesystem using the same drive number which was passed to :cpp:func:`esp_vfs_fat_register`. See `FatFs documentation for more details <http://www.elm-chan.org/fsw/ff/doc/mount.html>`.
4. Call POSIX and C standard library functions to open, read, write, erase, copy files, etc. Use paths starting with the prefix passed to ``esp_vfs_register`` (such as ``"/sdcard/hello.txt"``).
4. Call POSIX and C standard library functions to open, read, write, erase, copy files, etc. Use paths starting with the prefix passed to :cpp:func:`esp_vfs_register` (such as ``"/sdcard/hello.txt"``).
5. Optionally, call FatFs library functions directly. Use paths without a VFS prefix in this case (``"/hello.txt"``).
6. Close all open files.
7. Call ``f_mount`` function for the same drive number, with NULL ``FATFS*`` argument, to unmount the filesystem.
7. Call FatFs ``f_mount`` function for the same drive number, with NULL ``FATFS*`` argument, to unmount the filesystem.
8. Call ``ff_diskio_register`` with NULL ``ff_diskio_impl_t*`` argument and the same drive number.
8. Call FatFs :cpp:func:`ff_diskio_register` with NULL ``ff_diskio_impl_t*`` argument and the same drive number.
9. Call ``esp_vfs_fat_unregister_path`` with the path where the file system is mounted to remove FatFs from VFS, and free the ``FATFS`` structure allocated on step 1.
9. Call :cpp:func:`esp_vfs_fat_unregister_path` with the path where the file system is mounted to remove FatFs from VFS, and free the ``FATFS`` structure allocated on step 1.
Convenience functions, ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_unmount``, which wrap these steps and also handle SD card initialization, are described in the next section.
@ -39,19 +39,28 @@ Convenience functions, ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_unmou
Using FatFs with VFS and SD cards
---------------------------------
``esp_vfs_fat.h`` header file also provides a convenience function to perform steps 13 and 79, and also handle SD card initialization: ``esp_vfs_fat_sdmmc_mount``. This function does only limited error handling. Developers are encouraged to look at its source code and incorporate more advanced versions into production applications. ``esp_vfs_fat_sdmmc_unmount`` function unmounts the filesystem and releases resources acquired by ``esp_vfs_fat_sdmmc_mount``.
:component_file:`fatfs/src/esp_vfs_fat.h` header file also provides a convenience function to perform steps 13 and 79, and also handle SD card initialization: :cpp:func:`esp_vfs_fat_sdmmc_mount`. This function does only limited error handling. Developers are encouraged to look at its source code and incorporate more advanced versions into production applications. :cpp:func:`esp_vfs_fat_sdmmc_unmount` function unmounts the filesystem and releases resources acquired by :cpp:func:`esp_vfs_fat_sdmmc_mount`.
.. doxygenfunction:: esp_vfs_fat_sdmmc_mount
.. doxygenstruct:: esp_vfs_fat_mount_config_t
:members:
.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount
Using FatFs with VFS in read-only mode
--------------------------------------
Convenience functions, :cpp:func:`esp_vfs_fat_rawflash_mount` and :cpp:func:`esp_vfs_fat_rawflash_unmount`, are provided by :component_file:`fatfs/src/esp_vfs_fat.h` header file in order to perform steps 1-3 and 7-9 for read-only FAT partitions. These are particularly helpful for data partitions written only once during factory provisioning and need not be changed by production application throughout the lifetime.
.. doxygenfunction:: esp_vfs_fat_rawflash_mount
.. doxygenfunction:: esp_vfs_fat_rawflash_unmount
FatFS disk IO layer
-------------------
FatFs has been extended with an API to register disk IO driver at runtime.
Implementation of disk IO functions for SD/MMC cards is provided. It can be registered for the given FatFs drive number using ``ff_diskio_register_sdmmc`` function.
Implementation of disk IO functions for SD/MMC cards is provided. It can be registered for the given FatFs drive number using :cpp:func:`ff_diskio_register_sdmmc` function.
.. doxygenfunction:: ff_diskio_register
.. doxygenstruct:: ff_diskio_impl_t

View file

@ -207,7 +207,7 @@ class AssignTest(object):
else:
failed_to_assign.append(group)
if failed_to_assign:
console_log("Please add the following jobs to .gitlab-ci.yml with specific tags:", "R")
console_log("Too many test cases vs jobs to run. Please add the following jobs to .gitlab-ci.yml with specific tags:", "R")
for group in failed_to_assign:
console_log("* Add job with: " + ",".join(group.ci_job_match_keys), "R")
raise RuntimeError("Failed to assign test case to CI jobs")

View file

@ -68,7 +68,7 @@ Unit test jobs will do reset before running each case (because some cases do not
Gitlab CI do not support create jobs at runtime. We must maunally add all jobs to CI config file. To make test running in parallel, we limit the number of cases running on each job. When add new unit test cases, it could exceed the limitation that current unit test jobs support. In this case, assign test job will raise error, remind you to add jobs to `.gitlab-ci.yml`.
```
Please add the following jobs to .gitlab-ci.yml with specific tags:
Too many test cases vs jobs to run. Please add the following jobs to .gitlab-ci.yml with specific tags:
* Add job with: UT_T1_1, ESP32_IDF, psram
* Add job with: UT_T1_1, ESP32_IDF
```