From dd87deb2781a1eb2d70122816d5061199b059812 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Thu, 7 Sep 2017 17:37:59 +0300 Subject: [PATCH] Add SPIFFS Component to IDF --- .gitmodules | 4 + components/fatfs/src/vfs_fat_spiflash.c | 5 +- components/spiffs/Kconfig | 139 ++++ components/spiffs/component.mk | 3 + components/spiffs/esp_spiffs.c | 766 ++++++++++++++++++ components/spiffs/include/esp_spiffs.h | 94 +++ components/spiffs/include/spiffs_config.h | 313 +++++++ components/spiffs/spiffs | 1 + components/spiffs/test/component.mk | 1 + components/spiffs/test/test_spiffs.c | 506 ++++++++++++ docs/Doxyfile | 2 + docs/api-reference/storage/index.rst | 1 + docs/api-reference/storage/spiffs.rst | 53 ++ examples/storage/spiffs/Makefile | 9 + examples/storage/spiffs/README.md | 27 + examples/storage/spiffs/main/component.mk | 4 + .../storage/spiffs/main/spiffs_example_main.c | 99 +++ .../storage/spiffs/partitions_example.csv | 6 + examples/storage/spiffs/sdkconfig.defaults | 5 + tools/ci/mirror-list.txt | 1 + .../partition_table_unit_test_app.csv | 4 +- 21 files changed, 2039 insertions(+), 4 deletions(-) create mode 100644 components/spiffs/Kconfig create mode 100644 components/spiffs/component.mk create mode 100644 components/spiffs/esp_spiffs.c create mode 100644 components/spiffs/include/esp_spiffs.h create mode 100755 components/spiffs/include/spiffs_config.h create mode 160000 components/spiffs/spiffs create mode 100644 components/spiffs/test/component.mk create mode 100644 components/spiffs/test/test_spiffs.c create mode 100644 docs/api-reference/storage/spiffs.rst create mode 100644 examples/storage/spiffs/Makefile create mode 100644 examples/storage/spiffs/README.md create mode 100644 examples/storage/spiffs/main/component.mk create mode 100644 examples/storage/spiffs/main/spiffs_example_main.c create mode 100644 examples/storage/spiffs/partitions_example.csv create mode 100644 examples/storage/spiffs/sdkconfig.defaults diff --git a/.gitmodules b/.gitmodules index c54435224f..e118821b73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,7 @@ [submodule "components/libsodium/libsodium"] path = components/libsodium/libsodium url = https://github.com/jedisct1/libsodium.git + +[submodule "components/spiffs/spiffs"] + path = components/spiffs/spiffs + url = https://github.com/pellepl/spiffs.git diff --git a/components/fatfs/src/vfs_fat_spiflash.c b/components/fatfs/src/vfs_fat_spiflash.c index b9f80354a1..2e90468d2b 100644 --- a/components/fatfs/src/vfs_fat_spiflash.c +++ b/components/fatfs/src/vfs_fat_spiflash.c @@ -32,7 +32,10 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path, const size_t workbuf_size = 4096; void *workbuf = NULL; - esp_partition_t *data_partition = (esp_partition_t *)esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label); + esp_partition_subtype_t subtype = partition_label ? + ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_FAT; + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + subtype, 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; diff --git a/components/spiffs/Kconfig b/components/spiffs/Kconfig new file mode 100644 index 0000000000..d82ceec73e --- /dev/null +++ b/components/spiffs/Kconfig @@ -0,0 +1,139 @@ +menu "SPIFFS Configuration" + +config SPIFFS_MAX_PARTITIONS + int "Maximum Number of Partitions" + default 3 + range 1 10 + help + Define maximum number of partitions + that can be mounted. + +menu "SPIFFS Cache Configuration" +config SPIFFS_CACHE + bool "Enable SPIFFS Cache" + default "y" + help + Enables/disable memory read + caching of nucleus file system + operations. + +config SPIFFS_CACHE_WR + bool "Enable SPIFFS Write Caching" + default "y" + depends on SPIFFS_CACHE + help + Enables memory write caching for + file descriptors in hydrogen. + +config SPIFFS_CACHE_STATS + bool "Enable SPIFFS Cache Statistics" + default "n" + depends on SPIFFS_CACHE + help + Enable/disable statistics on caching. + Debug/test purpose only. + +endmenu + +config SPIFFS_PAGE_CHECK + bool "Enable SPIFFS Page Check" + default "y" + help + Always check header of each + accessed page to ensure consistent state. + If enabled it will increase number + of reads, will increase flash. + +config SPIFFS_GC_MAX_RUNS + int "Set Maximum GC Runs" + default 10 + range 1 255 + help + Define maximum number of gc runs to + perform to reach desired free pages. + +config SPIFFS_GC_STATS + bool "Enable SPIFFS GC Statistics" + default "n" + help + Enable/disable statistics on gc. + Debug/test purpose only. + +config SPIFFS_OBJ_NAME_LEN + int "Set SPIFFS Maximum Name Length" + default 32 + range 1 256 + help + Object name maximum length. Note that this length + include the zero-termination character, + meaning maximum string of characters can at most be + SPIFFS_OBJ_NAME_LEN - 1. + +config SPIFFS_USE_MAGIC + bool "Enable SPIFFS Filesystem Magic" + default "y" + help + Enable this to have an identifiable spiffs filesystem. + This will look for a magic in all sectors + to determine if this is a valid spiffs system + or not on mount point. + +config SPIFFS_USE_MAGIC_LENGTH + bool "Enable SPIFFS Filesystem Length Magic" + default "y" + depends on SPIFFS_USE_MAGIC + help + If this option is enabled, the magic will also be dependent + on the length of the filesystem. For example, a filesystem + configured and formatted for 4 megabytes will not be accepted + for mounting with a configuration defining the filesystem as 2 megabytes. + +menu "Debug Configuration" + +config SPIFFS_DBG + bool "Enable general SPIFFS debug" + default "n" + help + Enabling this option will print + general debug mesages to the console + +config SPIFFS_API_DBG + bool "Enable SPIFFS API debug" + default "n" + help + Enabling this option will print + API debug mesages to the console + +config SPIFFS_GC_DBG + bool "Enable SPIFFS Garbage Cleaner debug" + default "n" + help + Enabling this option will print + GC debug mesages to the console + +config SPIFFS_CACHE_DBG + bool "Enable SPIFFS Cache debug" + default "n" + depends on SPIFFS_CACHE + help + Enabling this option will print + Cache debug mesages to the console + +config SPIFFS_CHECK_DBG + bool "Enable SPIFFS Filesystem Check debug" + default "n" + help + Enabling this option will print + Filesystem Check debug mesages + to the console + +config SPIFFS_TEST_VISUALISATION + bool "Enable SPIFFS Filesystem Visualization" + default "n" + help + Enable this option to enable SPIFFS_vis function + in the api. + +endmenu + +endmenu diff --git a/components/spiffs/component.mk b/components/spiffs/component.mk new file mode 100644 index 0000000000..624b219d3d --- /dev/null +++ b/components/spiffs/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := spiffs/src +COMPONENT_SRCDIRS := . spiffs/src diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c new file mode 100644 index 0000000000..f731f5b005 --- /dev/null +++ b/components/spiffs/esp_spiffs.c @@ -0,0 +1,766 @@ +// Copyright 2015-2017 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 "esp_spiffs.h" +#include "spiffs.h" +#include "spiffs_nucleus.h" +#include "esp_log.h" +#include "esp_partition.h" +#include "esp_spi_flash.h" +#include "esp_image_format.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_err.h" +#include "rom/spi_flash.h" + +static const char * TAG = "SPIFFS"; + +/** + * @brief SPIFFS definition structure + */ +typedef struct { + spiffs *fs; /*!< Handle to the underlying SPIFFS */ + SemaphoreHandle_t lock; /*!< FS lock */ + const esp_partition_t* partition; /*!< The partition on which SPIFFS is located */ + char base_path[ESP_VFS_PATH_MAX+1]; /*!< Mount point */ + bool by_label; /*!< Partition was mounted by label */ + spiffs_config cfg; /*!< SPIFFS Mount configuration */ + uint8_t *work; /*!< Work Buffer */ + uint8_t *fds; /*!< File Descriptor Buffer */ + uint32_t fds_sz; /*!< File Descriptor Buffer Length */ + uint8_t *cache; /*!< Cache Buffer */ + uint32_t cache_sz; /*!< Cache Buffer Length */ +} esp_spiffs_t; + +/** + * @brief SPIFFS DIR structure + */ +typedef struct { + DIR dir; /*!< VFS DIR struct */ + spiffs_DIR d; /*!< SPIFFS DIR struct */ + struct dirent e; /*!< Last open dirent */ + long offset; /*!< Offset of the current dirent */ + char path[SPIFFS_OBJ_NAME_LEN]; /*!< Requested directory name */ +} vfs_spiffs_dir_t; + +static int vfs_spiffs_open(void* ctx, const char * path, int flags, int mode); +static ssize_t vfs_spiffs_write(void* ctx, int fd, const void * data, size_t size); +static ssize_t vfs_spiffs_read(void* ctx, int fd, void * dst, size_t size); +static int vfs_spiffs_close(void* ctx, int fd); +static off_t vfs_spiffs_lseek(void* ctx, int fd, off_t offset, int mode); +static int vfs_spiffs_fstat(void* ctx, int fd, struct stat * st); +static int vfs_spiffs_stat(void* ctx, const char * path, struct stat * st); +static int vfs_spiffs_unlink(void* ctx, const char *path); +static int vfs_spiffs_link(void* ctx, const char* n1, const char* n2); +static int vfs_spiffs_rename(void* ctx, const char *src, const char *dst); +static DIR* vfs_spiffs_opendir(void* ctx, const char* name); +static int vfs_spiffs_closedir(void* ctx, DIR* pdir); +static struct dirent* vfs_spiffs_readdir(void* ctx, DIR* pdir); +static int vfs_spiffs_readdir_r(void* ctx, DIR* pdir, + struct dirent* entry, struct dirent** out_dirent); +static long vfs_spiffs_telldir(void* ctx, DIR* pdir); +static void vfs_spiffs_seekdir(void* ctx, DIR* pdir, long offset); +static int vfs_spiffs_mkdir(void* ctx, const char* name, mode_t mode); +static int vfs_spiffs_rmdir(void* ctx, const char* name); + +static esp_spiffs_t * _efs[CONFIG_SPIFFS_MAX_PARTITIONS]; + +void spiffs_api_lock(spiffs *fs) +{ + xSemaphoreTake(((esp_spiffs_t *)(fs->user_data))->lock, portMAX_DELAY); +} + +void spiffs_api_unlock(spiffs *fs) +{ + xSemaphoreGive(((esp_spiffs_t *)(fs->user_data))->lock); +} + +static s32_t spiffs_api_read(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *dst) +{ + esp_err_t err = esp_partition_read(((esp_spiffs_t *)(fs->user_data))->partition, + addr, dst, size); + if (err) { + ESP_LOGE(TAG, "failed to read addr %08x, size %08x, err %d", addr, size, err); + return -1; + } + return 0; +} + +static s32_t spiffs_api_write(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *src) +{ + esp_err_t err = esp_partition_write(((esp_spiffs_t *)(fs->user_data))->partition, + addr, src, size); + if (err) { + ESP_LOGE(TAG, "failed to write addr %08x, size %08x, err %d", addr, size, err); + return -1; + } + return 0; +} + +static s32_t spiffs_api_erase(spiffs *fs, uint32_t addr, uint32_t size) +{ + esp_err_t err = esp_partition_erase_range(((esp_spiffs_t *)(fs->user_data))->partition, + addr, size); + if (err) { + ESP_LOGE(TAG, "failed to erase addr %08x, size %08x, err %d", addr, size, err); + return -1; + } + return 0; +} + +static void spiffs_api_check(spiffs *fs, spiffs_check_type type, + spiffs_check_report report, uint32_t arg1, uint32_t arg2) +{ + static const char * spiffs_check_type_str[3] = { + "LOOKUP", + "INDEX", + "PAGE" + }; + + static const char * spiffs_check_report_str[7] = { + "PROGRESS", + "ERROR", + "FIX INDEX", + "FIX LOOKUP", + "DELETE ORPHANED INDEX", + "DELETE PAGE", + "DELETE BAD FILE" + }; + + if (report != SPIFFS_CHECK_PROGRESS) { + ESP_LOGE(TAG, "CHECK: type:%s, report:%s, %x:%x", spiffs_check_type_str[type], + spiffs_check_report_str[report], arg1, arg2); + } else { + ESP_LOGV(TAG, "CHECK PROGRESS: report:%s, %x:%x", + spiffs_check_report_str[report], arg1, arg2); + } +} + +static void esp_spiffs_free(esp_spiffs_t ** efs) +{ + esp_spiffs_t * e = *efs; + if (*efs == NULL) { + return; + } + *efs = NULL; + + if (e->fs) { + SPIFFS_unmount(e->fs); + free(e->fs); + } + vSemaphoreDelete(e->lock); + free(e->fds); + free(e->cache); + free(e->work); + free(e); +} + +static esp_err_t esp_spiffs_by_label(const char* label, int * index){ + int i; + esp_spiffs_t * p; + for (i = 0; i < CONFIG_SPIFFS_MAX_PARTITIONS; i++) { + p = _efs[i]; + if (p) { + if (!label && !p->by_label) { + *index = i; + return ESP_OK; + } + if (label && p->by_label && strncmp(label, p->partition->label, 17) == 0) { + *index = i; + return ESP_OK; + } + } + } + return ESP_ERR_NOT_FOUND; +} + +static esp_err_t esp_spiffs_get_empty(int * index){ + int i; + for (i = 0; i < CONFIG_SPIFFS_MAX_PARTITIONS; i++) { + if (_efs[i] == NULL) { + *index = i; + return ESP_OK; + } + } + return ESP_ERR_NOT_FOUND; +} + +static esp_err_t esp_spiffs_init(const esp_vfs_spiffs_conf_t* conf) +{ + int index; + //find if such partition is already mounted + if (esp_spiffs_by_label(conf->partition_label, &index) == ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + + if (esp_spiffs_get_empty(&index) != ESP_OK) { + ESP_LOGE(TAG, "max mounted partitions reached"); + return ESP_ERR_INVALID_STATE; + } + + esp_partition_subtype_t subtype = conf->partition_label ? + ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_SPIFFS; + const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + subtype, conf->partition_label); + if (!partition) { + ESP_LOGE(TAG, "spiffs partition could not be found"); + return ESP_ERR_NOT_FOUND; + } + + if (partition->encrypted) { + ESP_LOGE(TAG, "spiffs can not run on encrypted partition"); + return ESP_ERR_INVALID_STATE; + } + + esp_spiffs_t * efs = malloc(sizeof(esp_spiffs_t)); + if (efs == NULL) { + ESP_LOGE(TAG, "esp_spiffs could not be malloced"); + return ESP_ERR_NO_MEM; + } + memset(efs, 0, sizeof(esp_spiffs_t)); + + efs->cfg.hal_erase_f = spiffs_api_erase; + efs->cfg.hal_read_f = spiffs_api_read; + efs->cfg.hal_write_f = spiffs_api_write; + efs->cfg.log_block_size = g_rom_flashchip.sector_size; + efs->cfg.log_page_size = g_rom_flashchip.page_size; + efs->cfg.phys_addr = 0; + efs->cfg.phys_erase_block = g_rom_flashchip.sector_size; + efs->cfg.phys_size = partition->size; + + efs->by_label = conf->partition_label != NULL; + + efs->lock = xSemaphoreCreateMutex(); + if (efs->lock == NULL) { + ESP_LOGE(TAG, "mutex lock could not be created"); + esp_spiffs_free(&efs); + return ESP_ERR_NO_MEM; + } + + efs->fds_sz = conf->max_files * sizeof(spiffs_fd); + efs->fds = malloc(efs->fds_sz); + if (efs->fds == NULL) { + ESP_LOGE(TAG, "fd buffer could not be malloced"); + esp_spiffs_free(&efs); + return ESP_ERR_NO_MEM; + } + memset(efs->fds, 0, efs->fds_sz); + +#if SPIFFS_CACHE + efs->cache_sz = sizeof(spiffs_cache) + conf->max_files * (sizeof(spiffs_cache_page) + + efs->cfg.log_page_size); + efs->cache = malloc(efs->cache_sz); + if (efs->cache == NULL) { + ESP_LOGE(TAG, "cache buffer could not be malloced"); + esp_spiffs_free(&efs); + return ESP_ERR_NO_MEM; + } + memset(efs->cache, 0, efs->cache_sz); +#endif + + const uint32_t work_sz = efs->cfg.log_page_size * 2; + efs->work = malloc(work_sz); + if (efs->work == NULL) { + ESP_LOGE(TAG, "work buffer could not be malloced"); + esp_spiffs_free(&efs); + return ESP_ERR_NO_MEM; + } + memset(efs->work, 0, work_sz); + + efs->fs = malloc(sizeof(spiffs)); + if (efs->fs == NULL) { + ESP_LOGE(TAG, "spiffs could not be malloced"); + esp_spiffs_free(&efs); + return ESP_ERR_NO_MEM; + } + memset(efs->fs, 0, sizeof(spiffs)); + + efs->fs->user_data = (void *)efs; + efs->partition = partition; + + s32_t res = SPIFFS_mount(efs->fs, &efs->cfg, efs->work, efs->fds, efs->fds_sz, + efs->cache, efs->cache_sz, spiffs_api_check); + + if (conf->format_if_mount_failed && res != SPIFFS_OK) { + ESP_LOGW(TAG, "mount failed, %i. formatting...", SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + res = SPIFFS_format(efs->fs); + if (res != SPIFFS_OK) { + ESP_LOGE(TAG, "format failed, %i", SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + esp_spiffs_free(&efs); + return ESP_FAIL; + } + res = SPIFFS_mount(efs->fs, &efs->cfg, efs->work, efs->fds, efs->fds_sz, + efs->cache, efs->cache_sz, spiffs_api_check); + } + if (res != SPIFFS_OK) { + ESP_LOGE(TAG, "mount failed, %i", SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + esp_spiffs_free(&efs); + return ESP_FAIL; + } + _efs[index] = efs; + return ESP_OK; +} + +bool esp_spiffs_mounted(const char* partition_label) +{ + int index; + if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) { + return false; + } + return (SPIFFS_mounted(_efs[index]->fs)); +} + +esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes) +{ + int index; + if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + SPIFFS_info(_efs[index]->fs, total_bytes, used_bytes); + return ESP_OK; +} + +esp_err_t esp_spiffs_format(const char* partition_label) +{ + bool mount_on_success = false; + int index; + esp_err_t err = esp_spiffs_by_label(partition_label, &index); + if (err != ESP_OK) { + esp_vfs_spiffs_conf_t conf = { + .format_if_mount_failed = true, + .partition_label = partition_label, + .max_files = 1 + }; + err = esp_spiffs_init(&conf); + if (err != ESP_OK) { + return err; + } + err = esp_spiffs_by_label(partition_label, &index); + if (err != ESP_OK) { + return err; + } + esp_spiffs_free(&_efs[index]); + return ESP_OK; + } else if (SPIFFS_mounted(_efs[index]->fs)) { + SPIFFS_unmount(_efs[index]->fs); + mount_on_success = true; + } + s32_t res = SPIFFS_format(_efs[index]->fs); + if (res != SPIFFS_OK) { + ESP_LOGE(TAG, "format failed, %i", SPIFFS_errno(_efs[index]->fs)); + SPIFFS_clearerr(_efs[index]->fs); + return ESP_FAIL; + } + + if (mount_on_success) { + res = SPIFFS_mount(_efs[index]->fs, &_efs[index]->cfg, _efs[index]->work, + _efs[index]->fds, _efs[index]->fds_sz, _efs[index]->cache, + _efs[index]->cache_sz, spiffs_api_check); + if (res != SPIFFS_OK) { + ESP_LOGE(TAG, "mount failed, %i", SPIFFS_errno(_efs[index]->fs)); + SPIFFS_clearerr(_efs[index]->fs); + return ESP_FAIL; + } + } + return ESP_OK; +} + +esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) +{ + assert(conf->base_path); + const esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &vfs_spiffs_write, + .lseek_p = &vfs_spiffs_lseek, + .read_p = &vfs_spiffs_read, + .open_p = &vfs_spiffs_open, + .close_p = &vfs_spiffs_close, + .fstat_p = &vfs_spiffs_fstat, + .stat_p = &vfs_spiffs_stat, + .link_p = &vfs_spiffs_link, + .unlink_p = &vfs_spiffs_unlink, + .rename_p = &vfs_spiffs_rename, + .opendir_p = &vfs_spiffs_opendir, + .closedir_p = &vfs_spiffs_closedir, + .readdir_p = &vfs_spiffs_readdir, + .readdir_r_p = &vfs_spiffs_readdir_r, + .seekdir_p = &vfs_spiffs_seekdir, + .telldir_p = &vfs_spiffs_telldir, + .mkdir_p = &vfs_spiffs_mkdir, + .rmdir_p = &vfs_spiffs_rmdir + }; + + esp_err_t err = esp_spiffs_init(conf); + if (err != ESP_OK) { + return err; + } + + int index; + if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + + strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1); + err = esp_vfs_register(conf->base_path, &vfs, _efs[index]); + if (err != ESP_OK) { + esp_spiffs_free(&_efs[index]); + return err; + } + + return ESP_OK; +} + +esp_err_t esp_vfs_spiffs_unregister(const char* partition_label) +{ + int index; + if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = esp_vfs_unregister(_efs[index]->base_path); + if (err != ESP_OK) { + return err; + } + esp_spiffs_free(&_efs[index]); + return ESP_OK; +} + +static int spiffs_res_to_errno(s32_t fr) +{ + switch(fr) { + case SPIFFS_OK : + return 0; + case SPIFFS_ERR_NOT_MOUNTED : + return ENODEV; + case SPIFFS_ERR_NOT_A_FS : + return ENODEV; + case SPIFFS_ERR_FULL : + return ENOSPC; + case SPIFFS_ERR_BAD_DESCRIPTOR : + return EBADF; + case SPIFFS_ERR_MOUNTED : + return EEXIST; + case SPIFFS_ERR_FILE_EXISTS : + return EEXIST; + case SPIFFS_ERR_NOT_FOUND : + return ENOENT; + case SPIFFS_ERR_NOT_A_FILE : + return ENOENT; + case SPIFFS_ERR_DELETED : + return ENOENT; + case SPIFFS_ERR_FILE_DELETED : + return ENOENT; + case SPIFFS_ERR_NAME_TOO_LONG : + return ENAMETOOLONG; + case SPIFFS_ERR_RO_NOT_IMPL : + return EROFS; + case SPIFFS_ERR_RO_ABORTED_OPERATION : + return EROFS; + default : + return EIO; + } + return ENOTSUP; +} + +static int spiffs_mode_conv(int m) +{ + int res = 0; + int acc_mode = m & O_ACCMODE; + if (acc_mode == O_RDONLY) { + res |= SPIFFS_O_RDONLY; + } else if (acc_mode == O_WRONLY) { + res |= SPIFFS_O_WRONLY; + } else if (acc_mode == O_RDWR) { + res |= SPIFFS_O_RDWR; + } + if ((m & O_CREAT) && (m & O_EXCL)) { + res |= SPIFFS_O_CREAT | SPIFFS_O_EXCL; + } else if ((m & O_CREAT) && (m & O_TRUNC)) { + res |= SPIFFS_O_CREAT | SPIFFS_O_TRUNC; + } else if (m & O_APPEND) { + res |= SPIFFS_O_APPEND; + } + return res; +} + +static int vfs_spiffs_open(void* ctx, const char * path, int flags, int mode) +{ + assert(path); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + int fd = SPIFFS_open(efs->fs, path, spiffs_mode_conv(flags), mode); + if (fd < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return fd; +} + +static ssize_t vfs_spiffs_write(void* ctx, int fd, const void * data, size_t size) +{ + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + ssize_t res = SPIFFS_write(efs->fs, fd, (void *)data, size); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static ssize_t vfs_spiffs_read(void* ctx, int fd, void * dst, size_t size) +{ + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + ssize_t res = SPIFFS_read(efs->fs, fd, dst, size); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static int vfs_spiffs_close(void* ctx, int fd) +{ + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + int res = SPIFFS_close(efs->fs, fd); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static off_t vfs_spiffs_lseek(void* ctx, int fd, off_t offset, int mode) +{ + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + off_t res = SPIFFS_lseek(efs->fs, fd, offset, mode); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; + +} + +static int vfs_spiffs_fstat(void* ctx, int fd, struct stat * st) +{ + assert(st); + spiffs_stat s; + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + off_t res = SPIFFS_fstat(efs->fs, fd, &s); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + st->st_size = s.size; + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG; + return res; +} + +static int vfs_spiffs_stat(void* ctx, const char * path, struct stat * st) +{ + assert(path); + assert(st); + spiffs_stat s; + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + off_t res = SPIFFS_stat(efs->fs, path, &s); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + + st->st_size = s.size; + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO; + st->st_mode |= (s.type == SPIFFS_TYPE_DIR)?S_IFDIR:S_IFREG; + return res; +} + +static int vfs_spiffs_rename(void* ctx, const char *src, const char *dst) +{ + assert(src); + assert(dst); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + int res = SPIFFS_rename(efs->fs, src, dst); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static int vfs_spiffs_unlink(void* ctx, const char *path) +{ + assert(path); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + int res = SPIFFS_remove(efs->fs, path); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static DIR* vfs_spiffs_opendir(void* ctx, const char* name) +{ + assert(name); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + vfs_spiffs_dir_t * dir = calloc(1, sizeof(vfs_spiffs_dir_t)); + if (!dir) { + errno = ENOMEM; + return NULL; + } + if (!SPIFFS_opendir(efs->fs, name, &dir->d)) { + free(dir); + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return NULL; + } + dir->offset = 0; + strlcpy(dir->path, name, SPIFFS_OBJ_NAME_LEN); + return (DIR*) dir; +} + +static int vfs_spiffs_closedir(void* ctx, DIR* pdir) +{ + assert(pdir); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir; + int res = SPIFFS_closedir(&dir->d); + free(dir); + if (res < 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + return res; +} + +static struct dirent* vfs_spiffs_readdir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir; + struct dirent* out_dirent; + int err = vfs_spiffs_readdir_r(ctx, pdir, &dir->e, &out_dirent); + if (err != 0) { + errno = err; + return NULL; + } + return out_dirent; +} + +static int vfs_spiffs_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, + struct dirent** out_dirent) +{ + assert(pdir); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir; + struct spiffs_dirent out; + if (SPIFFS_readdir(&dir->d, &out) == 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + if (!errno) { + *out_dirent = NULL; + } + return errno; + } + const char * item_name = (const char *)out.name; + size_t plen = strlen(dir->path); + if (plen > 1) { + if (strncasecmp(dir->path, (const char *)out.name, plen) || out.name[plen] != '/' || !out.name[plen+1]) { + return vfs_spiffs_readdir_r(ctx, pdir, entry, out_dirent); + } + item_name += plen + 1; + } else if (item_name[0] == '/') { + item_name++; + } + entry->d_ino = 0; + entry->d_type = out.type; + snprintf(entry->d_name, SPIFFS_OBJ_NAME_LEN, "%s", item_name); + dir->offset++; + *out_dirent = entry; + return 0; +} + +static long vfs_spiffs_telldir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir; + return dir->offset; +} + +static void vfs_spiffs_seekdir(void* ctx, DIR* pdir, long offset) +{ + assert(pdir); + esp_spiffs_t * efs = (esp_spiffs_t *)ctx; + vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir; + struct spiffs_dirent tmp; + if (offset < dir->offset) { + //rewind dir + SPIFFS_closedir(&dir->d); + if (!SPIFFS_opendir(efs->fs, NULL, &dir->d)) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return; + } + dir->offset = 0; + } + while (dir->offset < offset) { + if (SPIFFS_readdir(&dir->d, &tmp) == 0) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return; + } + size_t plen = strlen(dir->path); + if (plen > 1) { + if (strncasecmp(dir->path, (const char *)tmp.name, plen) || tmp.name[plen] != '/' || !tmp.name[plen+1]) { + continue; + } + } + dir->offset++; + } +} + +static int vfs_spiffs_mkdir(void* ctx, const char* name, mode_t mode) +{ + errno = ENOTSUP; + return -1; +} + +static int vfs_spiffs_rmdir(void* ctx, const char* name) +{ + errno = ENOTSUP; + return -1; +} + +static int vfs_spiffs_link(void* ctx, const char* n1, const char* n2) +{ + errno = ENOTSUP; + return -1; +} diff --git a/components/spiffs/include/esp_spiffs.h b/components/spiffs/include/esp_spiffs.h new file mode 100644 index 0000000000..9a1f12c437 --- /dev/null +++ b/components/spiffs/include/esp_spiffs.h @@ -0,0 +1,94 @@ +// Copyright 2015-2017 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 _ESP_SPIFFS_H_ +#define _ESP_SPIFFS_H_ + +#include +#include "esp_err.h" + +/** + * @brief Configuration structure for esp_vfs_spiffs_register + */ +typedef struct { + const char* base_path; /*!< File path prefix associated with the filesystem. */ + const char* partition_label; /*!< Optional, label of SPIFFS partition to use. If set to NULL, first partition with subtype=spiffs will be used. */ + size_t max_files; /*!< Maximum files that could be open at the same time. */ + bool format_if_mount_failed; /*!< If true, it will format the file system if it fails to mount. */ +} esp_vfs_spiffs_conf_t; + +/** + * Register and mount SPIFFS to VFS with given path prefix. + * + * @param conf Pointer to esp_vfs_spiffs_conf_t configuration structure + * + * @return + * - ESP_OK if success + * - ESP_ERR_NO_MEM if objects could not be allocated + * - ESP_ERR_INVALID_STATE if already mounted or partition is encrypted + * - ESP_ERR_NOT_FOUND if partition for SPIFFS was not found + * - ESP_FAIL if mount or format fails + */ +esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf); + +/** + * Unregister and unmount SPIFFS from VFS + * + * @param partition_label Optional, label of the partition to unregister. + * If not specified, first partition with subtype=spiffs is used. + * + * @return + * - ESP_OK if successful + * - ESP_ERR_INVALID_STATE already unregistered + */ +esp_err_t esp_vfs_spiffs_unregister(const char* partition_label); + +/** + * Check if SPIFFS is mounted + * + * @param partition_label Optional, label of the partition to check. + * If not specified, first partition with subtype=spiffs is used. + * + * @return + * - true if mounted + * - false if not mounted + */ +bool esp_spiffs_mounted(const char* partition_label); + +/** + * Format the SPIFFS partition + * + * @param partition_label Optional, label of the partition to format. + * If not specified, first partition with subtype=spiffs is used. + * @return + * - ESP_OK if successful + * - ESP_FAIL on error + */ +esp_err_t esp_spiffs_format(const char* partition_label); + +/** + * Get information for SPIFFS + * + * @param partition_label Optional, label of the partition to get info for. + * If not specified, first partition with subtype=spiffs is used. + * @param[out] total_bytes Size of the file system + * @param[out] used_bytes Current used bytes in the file system + * + * @return + * - ESP_OK if success + * - ESP_ERR_INVALID_STATE if not mounted + */ +esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes); + +#endif /* _ESP_SPIFFS_H_ */ diff --git a/components/spiffs/include/spiffs_config.h b/components/spiffs/include/spiffs_config.h new file mode 100755 index 0000000000..e0c9d7f9d2 --- /dev/null +++ b/components/spiffs/include/spiffs_config.h @@ -0,0 +1,313 @@ +/* + * spiffs_config.h + * + * Created on: Jul 3, 2013 + * Author: petera + */ + +#ifndef SPIFFS_CONFIG_H_ +#define SPIFFS_CONFIG_H_ + +// ----------- 8< ------------ +// Following includes are for the linux test build of spiffs +// These may/should/must be removed/altered/replaced in your target +#include +#include +#include +#include +#include +#include +#include +#include + +// compile time switches +#define SPIFFS_TAG "SPIFFS" + +// Set generic spiffs debug output call. +#if CONGIG_SPIFFS_DBG +#define SPIFFS_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#else +#define SPIFFS_DBG(...) +#endif +#if CONGIG_SPIFFS_API_DBG +#define SPIFFS_API_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#else +#define SPIFFS_API_DBG(...) +#endif +#if CONGIG_SPIFFS_DBG +#define SPIFFS_GC_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#else +#define SPIFFS_GC_DBG(...) +#endif +#if CONGIG_SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#else +#define SPIFFS_CACHE_DBG(...) +#endif +#if CONGIG_SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#else +#define SPIFFS_CHECK_DBG(...) +#endif + +// needed types +typedef signed int s32_t; +typedef unsigned int u32_t; +typedef signed short s16_t; +typedef unsigned short u16_t; +typedef signed char s8_t; +typedef unsigned char u8_t; + +struct spiffs_t; +extern void spiffs_api_lock(struct spiffs_t *fs); +extern void spiffs_api_unlock(struct spiffs_t *fs); + +// Defines spiffs debug print formatters +// some general signed number +#define _SPIPRIi "%d" +// address +#define _SPIPRIad "%08x" +// block +#define _SPIPRIbl "%04x" +// page +#define _SPIPRIpg "%04x" +// span index +#define _SPIPRIsp "%04x" +// file descriptor +#define _SPIPRIfd "%d" +// file object id +#define _SPIPRIid "%04x" +// file flags +#define _SPIPRIfl "%02x" + + +// Enable/disable API functions to determine exact number of bytes +// for filedescriptor and cache buffers. Once decided for a configuration, +// this can be disabled to reduce flash. +#define SPIFFS_BUFFER_HELP 0 + +// Enables/disable memory read caching of nucleus file system operations. +// If enabled, memory area must be provided for cache in SPIFFS_mount. +#ifdef CONFIG_SPIFFS_CACHE +#define SPIFFS_CACHE (1) +#else +#define SPIFFS_CACHE (0) +#endif +#if SPIFFS_CACHE +// Enables memory write caching for file descriptors in hydrogen +#ifdef CONFIG_SPIFFS_CACHE_WR +#define SPIFFS_CACHE_WR (1) +#else +#define SPIFFS_CACHE_WR (0) +#endif + +// Enable/disable statistics on caching. Debug/test purpose only. +#ifdef CONFIG_SPIFFS_CACHE_STATS +#define SPIFFS_CACHE_STATS (1) +#else +#define SPIFFS_CACHE_STATS (0) +#endif +#endif + +// Always check header of each accessed page to ensure consistent state. +// If enabled it will increase number of reads, will increase flash. +#ifdef CONFIG_SPIFFS_PAGE_CHECK +#define SPIFFS_PAGE_CHECK (1) +#else +#define SPIFFS_PAGE_CHECK (0) +#endif + +// Define maximum number of gc runs to perform to reach desired free pages. +#define SPIFFS_GC_MAX_RUNS CONFIG_SPIFFS_GC_MAX_RUNS + +// Enable/disable statistics on gc. Debug/test purpose only. +#ifdef CONFIG_SPIFFS_GC_STATS +#define SPIFFS_GC_STATS (1) +#else +#define SPIFFS_GC_STATS (0) +#endif + +// Garbage collecting examines all pages in a block which and sums up +// to a block score. Deleted pages normally gives positive score and +// used pages normally gives a negative score (as these must be moved). +// To have a fair wear-leveling, the erase age is also included in score, +// whose factor normally is the most positive. +// The larger the score, the more likely it is that the block will +// picked for garbage collection. + +// Garbage collecting heuristics - weight used for deleted pages. +#define SPIFFS_GC_HEUR_W_DELET (5) +// Garbage collecting heuristics - weight used for used pages. +#define SPIFFS_GC_HEUR_W_USED (-1) +// Garbage collecting heuristics - weight used for time between +// last erased and erase of this block. +#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) + +// Object name maximum length. Note that this length include the +// zero-termination character, meaning maximum string of characters +// can at most be SPIFFS_OBJ_NAME_LEN - 1. +#define SPIFFS_OBJ_NAME_LEN (CONFIG_SPIFFS_OBJ_NAME_LEN) + +// Maximum length of the metadata associated with an object. +// Setting to non-zero value enables metadata-related API but also +// changes the on-disk format, so the change is not backward-compatible. +// +// Do note: the meta length must never exceed +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// +// This is derived from following: +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + +// spiffs_object_ix_header fields + at least some LUT entries) +#define SPIFFS_OBJ_META_LEN (0) + +// Size of buffer allocated on stack used when copying data. +// Lower value generates more read/writes. No meaning having it bigger +// than logical page size. +#define SPIFFS_COPY_BUFFER_STACK (256) + +// Enable this to have an identifiable spiffs filesystem. This will look for +// a magic in all sectors to determine if this is a valid spiffs system or +// not on mount point. If not, SPIFFS_format must be called prior to mounting +// again. +#ifdef CONFIG_SPIFFS_USE_MAGIC +#define SPIFFS_USE_MAGIC (1) +#else +#define SPIFFS_USE_MAGIC (0) +#endif + +#if SPIFFS_USE_MAGIC +// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is +// enabled, the magic will also be dependent on the length of the filesystem. +// For example, a filesystem configured and formatted for 4 megabytes will not +// be accepted for mounting with a configuration defining the filesystem as 2 +// megabytes. +#ifdef CONFIG_SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_USE_MAGIC_LENGTH (1) +#else +#define SPIFFS_USE_MAGIC_LENGTH (0) +#endif +#endif + +// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level +// These should be defined on a multithreaded system + +// define this to enter a mutex if you're running on a multithreaded system +#define SPIFFS_LOCK(fs) spiffs_api_lock(fs) +// define this to exit a mutex if you're running on a multithreaded system +#define SPIFFS_UNLOCK(fs) spiffs_api_unlock(fs) + +// Enable if only one spiffs instance with constant configuration will exist +// on the target. This will reduce calculations, flash and memory accesses. +// Parts of configuration must be defined below instead of at time of mount. +#define SPIFFS_SINGLETON 0 + +// Enable this if your target needs aligned data for index tables +#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 0 + +// Enable this if you want the HAL callbacks to be called with the spiffs struct +#define SPIFFS_HAL_CALLBACK_EXTRA 1 + +// Enable this if you want to add an integer offset to all file handles +// (spiffs_file). This is useful if running multiple instances of spiffs on +// same target, in order to recognise to what spiffs instance a file handle +// belongs. +// NB: This adds config field fh_ix_offset in the configuration struct when +// mounting, which must be defined. +#define SPIFFS_FILEHDL_OFFSET 0 + +// Enable this to compile a read only version of spiffs. +// This will reduce binary size of spiffs. All code comprising modification +// of the file system will not be compiled. Some config will be ignored. +// HAL functions for erasing and writing to spi-flash may be null. Cache +// can be disabled for even further binary size reduction (and ram savings). +// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. +// If the file system cannot be mounted due to aborted erase operation and +// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be +// returned. +// Might be useful for e.g. bootloaders and such. +#define SPIFFS_READ_ONLY 0 + +// Enable this to add a temporal file cache using the fd buffer. +// The effects of the cache is that SPIFFS_open will find the file faster in +// certain cases. It will make it a lot easier for spiffs to find files +// opened frequently, reducing number of readings from the spi flash for +// finding those files. +// This will grow each fd by 6 bytes. If your files are opened in patterns +// with a degree of temporal locality, the system is optimized. +// Examples can be letting spiffs serve web content, where one file is the css. +// The css is accessed for each html file that is opened, meaning it is +// accessed almost every second time a file is opened. Another example could be +// a log file that is often opened, written, and closed. +// The size of the cache is number of given file descriptors, as it piggybacks +// on the fd update mechanism. The cache lives in the closed file descriptors. +// When closed, the fd know the whereabouts of the file. Instead of forgetting +// this, the temporal cache will keep handling updates to that file even if the +// fd is closed. If the file is opened again, the location of the file is found +// directly. If all available descriptors become opened, all cache memory is +// lost. +#define SPIFFS_TEMPORAL_FD_CACHE 1 + +// Temporal file cache hit score. Each time a file is opened, all cached files +// will lose one point. If the opened file is found in cache, that entry will +// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this +// value for the specific access patterns of the application. However, it must +// be between 1 (no gain for hitting a cached entry often) and 255. +#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4 + +// Enable to be able to map object indices to memory. +// This allows for faster and more deterministic reading if cases of reading +// large files and when changing file offset by seeking around a lot. +// When mapping a file's index, the file system will be scanned for index pages +// and the info will be put in memory provided by user. When reading, the +// memory map can be looked up instead of searching for index pages on the +// medium. This way, user can trade memory against performance. +// Whole, parts of, or future parts not being written yet can be mapped. The +// memory array will be owned by spiffs and updated accordingly during garbage +// collecting or when modifying the indices. The latter is invoked by when the +// file is modified in some way. The index buffer is tied to the file +// descriptor. +#define SPIFFS_IX_MAP 1 + +// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function +// in the api. This function will visualize all filesystem using given printf +// function. +#ifdef CONFIG_SPIFFS_TEST_VISUALISATION +#define SPIFFS_TEST_VISUALISATION 1 +#else +#define SPIFFS_TEST_VISUALISATION 0 +#endif +#if SPIFFS_TEST_VISUALISATION +#ifndef spiffs_printf +#define spiffs_printf(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__) +#endif +// spiffs_printf argument for a free page +#define SPIFFS_TEST_VIS_FREE_STR "_" +// spiffs_printf argument for a deleted page +#define SPIFFS_TEST_VIS_DELE_STR "/" +// spiffs_printf argument for an index page for given object id +#define SPIFFS_TEST_VIS_INDX_STR(id) "i" +// spiffs_printf argument for a data page for given object id +#define SPIFFS_TEST_VIS_DATA_STR(id) "d" +#endif + +// Types depending on configuration such as the amount of flash bytes +// given to spiffs file system in total (spiffs_file_system_size), +// the logical block size (log_block_size), and the logical page size +// (log_page_size) + +// Block index type. Make sure the size of this type can hold +// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size +typedef u16_t spiffs_block_ix; +// Page index type. Make sure the size of this type can hold +// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size +typedef u16_t spiffs_page_ix; +// Object id type - most significant bit is reserved for index flag. Make sure the +// size of this type can hold the highest object id on a full system, +// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 +typedef u16_t spiffs_obj_id; +// Object span index type. Make sure the size of this type can +// hold the largest possible span index on the system - +// i.e. (spiffs_file_system_size / log_page_size) - 1 +typedef u16_t spiffs_span_ix; + +#endif /* SPIFFS_CONFIG_H_ */ diff --git a/components/spiffs/spiffs b/components/spiffs/spiffs new file mode 160000 index 0000000000..794f0478d2 --- /dev/null +++ b/components/spiffs/spiffs @@ -0,0 +1 @@ +Subproject commit 794f0478d2aa9c978c3844da6e97f14239a1e061 diff --git a/components/spiffs/test/component.mk b/components/spiffs/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/spiffs/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/spiffs/test/test_spiffs.c b/components/spiffs/test/test_spiffs.c new file mode 100644 index 0000000000..d60e4a54c4 --- /dev/null +++ b/components/spiffs/test/test_spiffs.c @@ -0,0 +1,506 @@ +// Copyright 2015-2017 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 +#include +#include +#include +#include +#include +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_spiffs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_partition.h" + +const char* spiffs_test_hello_str = "Hello, World!\n"; +const char* spiffs_test_partition_label = "flash_test"; + +void test_spiffs_create_file_with_text(const char* name, const char* text) +{ + FILE* f = fopen(name, "wb"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_TRUE(fputs(text, f) != EOF); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_spiffs_overwrite_append(const char* filename) +{ + /* Create new file with 'aaaa' */ + test_spiffs_create_file_with_text(filename, "aaaa"); + + /* Append 'bbbb' to file */ + FILE *f_a = fopen(filename, "a"); + TEST_ASSERT_NOT_NULL(f_a); + TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a)); + TEST_ASSERT_EQUAL(0, fclose(f_a)); + + /* Read back 8 bytes from file, verify it's 'aaaabbbb' */ + char buf[10] = { 0 }; + FILE *f_r = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f_r); + TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r)); + TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8); + + /* Be sure we're at end of file */ + TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r)); + + TEST_ASSERT_EQUAL(0, fclose(f_r)); + + /* Overwrite file with 'cccc' */ + test_spiffs_create_file_with_text(filename, "cccc"); + + /* Verify file now only contains 'cccc' */ + f_r = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f_r); + bzero(buf, sizeof(buf)); + TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4 + TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4); + TEST_ASSERT_EQUAL(0, fclose(f_r)); +} + +void test_spiffs_read_file(const char* filename) +{ + FILE* f = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f); + char buf[32] = { 0 }; + int cb = fread(buf, 1, sizeof(buf), f); + TEST_ASSERT_EQUAL(strlen(spiffs_test_hello_str), cb); + TEST_ASSERT_EQUAL(0, strcmp(spiffs_test_hello_str, buf)); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_spiffs_open_max_files(const char* filename_prefix, size_t files_count) +{ + FILE** files = calloc(files_count, sizeof(FILE*)); + for (size_t i = 0; i < files_count; ++i) { + char name[32]; + snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i); + files[i] = fopen(name, "w"); + TEST_ASSERT_NOT_NULL(files[i]); + } + /* close everything and clean up */ + for (size_t i = 0; i < files_count; ++i) { + fclose(files[i]); + } + free(files); +} + +void test_spiffs_lseek(const char* filename) +{ + FILE* f = fopen(filename, "wb+"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); + TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); + TEST_ASSERT_EQUAL('9', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); + TEST_ASSERT_EQUAL('3', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); + TEST_ASSERT_EQUAL('8', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); + TEST_ASSERT_EQUAL(11, ftell(f)); + TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); + TEST_ASSERT_EQUAL(15, ftell(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); + char buf[20]; + TEST_ASSERT_EQUAL(15, fread(buf, 1, sizeof(buf), f)); + const char ref_buf[] = "0123456789\nabc\n"; + TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); + + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_spiffs_stat(const char* filename) +{ + test_spiffs_create_file_with_text(filename, "foo\n"); + struct stat st; + TEST_ASSERT_EQUAL(0, stat(filename, &st)); + TEST_ASSERT(st.st_mode & S_IFREG); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); +} + +void test_spiffs_unlink(const char* filename) +{ + test_spiffs_create_file_with_text(filename, "unlink\n"); + + TEST_ASSERT_EQUAL(0, unlink(filename)); + + TEST_ASSERT_NULL(fopen(filename, "r")); +} + +void test_spiffs_rename(const char* filename_prefix) +{ + char name_dst[64]; + char name_src[64]; + snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix); + snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix); + + unlink(name_dst); + unlink(name_src); + + FILE* f = fopen(name_src, "w+"); + TEST_ASSERT_NOT_NULL(f); + char* str = "0123456789"; + for (int i = 0; i < 400; ++i) { + TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); + } + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ASSERT_EQUAL(0, rename(name_src, name_dst)); + TEST_ASSERT_NULL(fopen(name_src, "r")); + FILE* fdst = fopen(name_dst, "r"); + TEST_ASSERT_NOT_NULL(fdst); + TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); + TEST_ASSERT_EQUAL(4000, ftell(fdst)); + TEST_ASSERT_EQUAL(0, fclose(fdst)); +} + +void test_spiffs_can_opendir(const char* path) +{ + char name_dir_file[64]; + const char * file_name = "test_opd.txt"; + snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", path, file_name); + unlink(name_dir_file); + test_spiffs_create_file_with_text(name_dir_file, "test_opendir\n"); + DIR* dir = opendir(path); + TEST_ASSERT_NOT_NULL(dir); + bool found = false; + while (true) { + struct dirent* de = readdir(dir); + if (!de) { + break; + } + if (strcasecmp(de->d_name, file_name) == 0) { + found = true; + break; + } + } + TEST_ASSERT_TRUE(found); + TEST_ASSERT_EQUAL(0, closedir(dir)); + unlink(name_dir_file); +} + +void test_spiffs_opendir_readdir_rewinddir(const char* dir_prefix) +{ + char name_dir_inner_file[64]; + char name_dir_inner[64]; + char name_dir_file3[64]; + char name_dir_file2[64]; + char name_dir_file1[64]; + + snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix); + snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix); + snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix); + snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix); + snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix); + + unlink(name_dir_inner_file); + rmdir(name_dir_inner); + unlink(name_dir_file1); + unlink(name_dir_file2); + unlink(name_dir_file3); + rmdir(dir_prefix); + + test_spiffs_create_file_with_text(name_dir_file1, "1\n"); + test_spiffs_create_file_with_text(name_dir_file2, "2\n"); + test_spiffs_create_file_with_text(name_dir_file3, "\01\02\03"); + test_spiffs_create_file_with_text(name_dir_inner_file, "3\n"); + + DIR* dir = opendir(dir_prefix); + 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/3.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "inner/3.txt"; + ++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)); +} + + +typedef struct { + const char* filename; + bool write; + size_t word_count; + int seed; + SemaphoreHandle_t done; + int result; +} read_write_test_arg_t; + +#define READ_WRITE_TEST_ARG_INIT(name, seed_) \ + { \ + .filename = name, \ + .seed = seed_, \ + .word_count = 4096, \ + .write = true, \ + .done = xSemaphoreCreateBinary() \ + } + +static void read_write_task(void* param) +{ + read_write_test_arg_t* args = (read_write_test_arg_t*) param; + FILE* f = fopen(args->filename, args->write ? "wb" : "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 val = rand(); + if (args->write) { + int cnt = fwrite(&val, sizeof(val), 1, f); + if (cnt != 1) { + ets_printf("E(w): i=%d, cnt=%d val=%d\n\n", i, cnt, val); + args->result = ESP_FAIL; + goto close; + } + } else { + uint32_t rval; + int cnt = fread(&rval, sizeof(rval), 1, f); + if (cnt != 1) { + ets_printf("E(r): i=%d, cnt=%d rval=%d\n\n", i, cnt, rval); + args->result = ESP_FAIL; + goto close; + } + } + } + args->result = ESP_OK; + +close: + fclose(f); + +done: + xSemaphoreGive(args->done); + vTaskDelay(1); + vTaskDelete(NULL); +} + +void test_spiffs_concurrent(const char* filename_prefix) +{ + char names[4][64]; + for (size_t i = 0; i < 4; ++i) { + snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1); + unlink(names[i]); + } + + read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1); + read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2); + + printf("writing f1 and f2\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 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(names[2], 3); + read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4); + + printf("reading f1 and f2, writing f3 and f4\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1); + xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 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); +} + + +static void test_setup() +{ + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = spiffs_test_partition_label, + .max_files = 5, + .format_if_mount_failed = true + }; + + TEST_ESP_OK(esp_vfs_spiffs_register(&conf)); +} + +static void test_teardown() +{ + TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_test_partition_label)); +} + +TEST_CASE("can format partition", "[spiffs]") +{ + const esp_partition_t* part = get_test_data_partition(); + TEST_ASSERT_NOT_NULL(part); + TEST_ESP_OK(esp_partition_erase_range(part, 0, part->size)); + test_setup(); + size_t total = 0, used = 0; + TEST_ESP_OK(esp_spiffs_info(spiffs_test_partition_label, &total, &used)); + printf("total: %d, used: %d\n", total, used); + TEST_ASSERT_EQUAL(0, used); + test_teardown(); +} + +TEST_CASE("can create and write file", "[spiffs]") +{ + test_setup(); + test_spiffs_create_file_with_text("/spiffs/hello.txt", spiffs_test_hello_str); + test_teardown(); +} + +TEST_CASE("can read file", "[spiffs]") +{ + test_setup(); + test_spiffs_create_file_with_text("/spiffs/hello.txt", spiffs_test_hello_str); + test_spiffs_read_file("/spiffs/hello.txt"); + test_teardown(); +} + +TEST_CASE("can open maximum number of files", "[spiffs]") +{ + size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */ + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = spiffs_test_partition_label, + .format_if_mount_failed = true, + .max_files = max_files + }; + TEST_ESP_OK(esp_vfs_spiffs_register(&conf)); + test_spiffs_open_max_files("/spiffs/f", max_files); + TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_test_partition_label)); +} + +TEST_CASE("overwrite and append file", "[spiffs]") +{ + test_setup(); + test_spiffs_overwrite_append("/spiffs/hello.txt"); + test_teardown(); +} + +TEST_CASE("can lseek", "[spiffs]") +{ + test_setup(); + test_spiffs_lseek("/spiffs/seek.txt"); + test_teardown(); +} + + +TEST_CASE("stat returns correct values", "[spiffs]") +{ + test_setup(); + test_spiffs_stat("/spiffs/stat.txt"); + test_teardown(); +} + +TEST_CASE("unlink removes a file", "[spiffs]") +{ + test_setup(); + test_spiffs_unlink("/spiffs/unlink.txt"); + test_teardown(); +} + +TEST_CASE("rename moves a file", "[spiffs]") +{ + test_setup(); + test_spiffs_rename("/spiffs/move"); + test_teardown(); +} + +TEST_CASE("can opendir root directory of FS", "[spiffs]") +{ + test_setup(); + test_spiffs_can_opendir("/spiffs"); + test_teardown(); +} + +TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[spiffs]") +{ + test_setup(); + test_spiffs_opendir_readdir_rewinddir("/spiffs/dir"); + test_teardown(); +} + +TEST_CASE("multiple tasks can use same volume", "[spiffs]") +{ + test_setup(); + test_spiffs_concurrent("/spiffs/f"); + test_teardown(); +} diff --git a/docs/Doxyfile b/docs/Doxyfile index fbc15cd267..628db925ac 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -91,6 +91,8 @@ INPUT = \ ../components/spi_flash/include/esp_spi_flash.h \ ../components/spi_flash/include/esp_partition.h \ ../components/bootloader_support/include/esp_flash_encrypt.h \ + ## SPIFFS + ../components/spiffs/include/esp_spiffs.h \ ## SD/MMC Card Host ## NOTE: for three lines below header_file.inc is not used ../components/sdmmc/include/sdmmc_cmd.h \ diff --git a/docs/api-reference/storage/index.rst b/docs/api-reference/storage/index.rst index 9c4394bd5a..9c9215b09f 100644 --- a/docs/api-reference/storage/index.rst +++ b/docs/api-reference/storage/index.rst @@ -10,6 +10,7 @@ Storage API Virtual Filesystem FAT Filesystem Wear Levelling + SPIFFS Filesystem Example code for this API section is provided in :example:`storage` directory of ESP-IDF examples. diff --git a/docs/api-reference/storage/spiffs.rst b/docs/api-reference/storage/spiffs.rst new file mode 100644 index 0000000000..d4630248c6 --- /dev/null +++ b/docs/api-reference/storage/spiffs.rst @@ -0,0 +1,53 @@ +SPIFFS Filesystem +================= + +Overview +-------- + +SPIFFS is a file system intended for SPI NOR flash devices on embedded targets. +It supports wear leveling, file system consistency checks and more. + +Notes +----- + + - Presently, spiffs does not support directories. It produces a flat structure. If SPIFFS is mounted under ``/spiffs`` creating a file with path ``/spiffs/tmp/myfile.txt`` will create a file called ``/tmp/myfile.txt`` in SPIFFS, instead of ``myfile.txt`` under directory ``/spiffs/tmp``. + - It is not a realtime stack. One write operation might last much longer than another. + - Presently, it does not detect or handle bad blocks. + +Tools +----- + +Host-Side tools for creating SPIFS partition images exist and one such tool is `mkspiffs `_. +You can use it to create image from a given folder and then flash that image with ``esptool.py`` + +To do that you need to obtain some parameters: + +- Block Size: 4096 (standard for SPI Flash) +- Page Size: 256 (standard for SPI Flash) +- Image Size: Size of the partition in bytes (can be obtained from partition table) +- Partition Offset: Starting address of the partition (can be obtained from partition table) + +To pack a folder into 1 Megabyte image:: + + mkspiffs -c [src_folder] -b 4096 -p 256 -s 0x100000 spiffs.bin + +To flash the image to ESP32 at offset 0x110000:: + + python esptool.py --chip esp32 --port [port] --baud [baud] write_flash -z 0x110000 spiffs.bin + +See also +-------- + +- :doc:`Partition Table documentation <../../api-guides/partition-tables>` + +Application Example +------------------- + +An example for using SPIFFS is provided in :example:`storage/spiffs` directory. This example initializes and mounts SPIFFS partition, and writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information. + +High level API Reference +------------------------ + +* :component_file:`spiffs/include/esp_spiffs.h` + +.. include:: /_build/inc/esp_spiffs.inc diff --git a/examples/storage/spiffs/Makefile b/examples/storage/spiffs/Makefile new file mode 100644 index 0000000000..4523423044 --- /dev/null +++ b/examples/storage/spiffs/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := spiffs + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/storage/spiffs/README.md b/examples/storage/spiffs/README.md new file mode 100644 index 0000000000..8c048fa7a4 --- /dev/null +++ b/examples/storage/spiffs/README.md @@ -0,0 +1,27 @@ +# SPIFFS example + +This example demonstrates how to use SPIFFS with ESP32. Example does the following steps: + +1. Use an "all-in-one" `esp_vfs_spiffs_register` function to: + - initialize SPIFFS, + - mount SPIFFS filesystem using SPIFFS library (and format, if the filesystem can not be mounted), + - register SPIFFS filesystem in VFS, enabling C standard library and POSIX functions to be used. +2. Create a file using `fopen` and write to it using `fprintf`. +3. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function. +4. Open renamed file for reading, read back the line, and print it to the terminal. + +## Example output + +Here is an example console output. In this case `format_if_mount_failed` parameter was set to `true` in the source code. SPIFFS was unformatted, so the initial mount has failed. SPIFFS was then formatted, and mounted again. + +``` +I (195) example: Initializing SPIFFS +E (195) SPIFFS: mount failed, -10025. formatting... +I (4525) example: Opening file +I (4635) example: File written +I (4685) example: Renaming file +I (4735) example: Reading file +I (4735) example: Read from file: 'Hello World!' +I (4735) example: SPIFFS unmounted +``` + diff --git a/examples/storage/spiffs/main/component.mk b/examples/storage/spiffs/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/storage/spiffs/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/storage/spiffs/main/spiffs_example_main.c b/examples/storage/spiffs/main/spiffs_example_main.c new file mode 100644 index 0000000000..d2ead7308b --- /dev/null +++ b/examples/storage/spiffs/main/spiffs_example_main.c @@ -0,0 +1,99 @@ +/* SPIFFS filesystem example. + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "esp_spiffs.h" + +static const char *TAG = "example"; + +void app_main(void) +{ + ESP_LOGI(TAG, "Initializing SPIFFS"); + + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = NULL, + .max_files = 5, + .format_if_mount_failed = true + }; + + // Use settings defined above to initialize and mount SPIFFS filesystem. + // Note: esp_vfs_spiffs_register is an all-in-one convenience function. + esp_err_t ret = esp_vfs_spiffs_register(&conf); + + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount or format filesystem"); + } else if (ret == ESP_ERR_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to find SPIFFS partition"); + } else { + ESP_LOGE(TAG, "Failed to initialize SPIFFS (%d)", ret); + } + return; + } + + size_t total = 0, used = 0; + ret = esp_spiffs_info(NULL, &total, &used); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SPIFFS partition information"); + } else { + ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); + } + + // Use POSIX and C standard library functions to work with files. + // First create a file. + ESP_LOGI(TAG, "Opening file"); + FILE* f = fopen("/spiffs/hello.txt", "w"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + fprintf(f, "Hello World!\n"); + fclose(f); + ESP_LOGI(TAG, "File written"); + + // Check if destination file exists before renaming + struct stat st; + if (stat("/spiffs/foo.txt", &st) == 0) { + // Delete it if it exists + unlink("/spiffs/foo.txt"); + } + + // Rename original file + ESP_LOGI(TAG, "Renaming file"); + if (rename("/spiffs/hello.txt", "/spiffs/foo.txt") != 0) { + ESP_LOGE(TAG, "Rename failed"); + return; + } + + // Open renamed file for reading + ESP_LOGI(TAG, "Reading file"); + f = fopen("/spiffs/foo.txt", "r"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + char line[64]; + fgets(line, sizeof(line), f); + fclose(f); + // strip newline + char* pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + ESP_LOGI(TAG, "Read from file: '%s'", line); + + // All done, unmount partition and disable SPIFFS + esp_vfs_spiffs_unregister(NULL); + ESP_LOGI(TAG, "SPIFFS unmounted"); +} diff --git a/examples/storage/spiffs/partitions_example.csv b/examples/storage/spiffs/partitions_example.csv new file mode 100644 index 0000000000..6d9ee2e745 --- /dev/null +++ b/examples/storage/spiffs/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, spiffs, , 0xF0000, diff --git a/examples/storage/spiffs/sdkconfig.defaults b/examples/storage/spiffs/sdkconfig.defaults new file mode 100644 index 0000000000..f30f322c62 --- /dev/null +++ b/examples/storage/spiffs/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_APP_OFFSET=0x10000 diff --git a/tools/ci/mirror-list.txt b/tools/ci/mirror-list.txt index 549359f60e..d3972aa5d5 100644 --- a/tools/ci/mirror-list.txt +++ b/tools/ci/mirror-list.txt @@ -6,5 +6,6 @@ components/esptool_py/esptool @GENERAL_MIRROR_SERVER@/idf/ components/libsodium/libsodium @GENERAL_MIRROR_SERVER@/idf/libsodium.git ALLOW_TO_SYNC_FROM_PUBLIC components/micro-ecc/micro-ecc @GENERAL_MIRROR_SERVER@/idf/micro-ecc.git ALLOW_TO_SYNC_FROM_PUBLIC components/nghttp/nghttp2 @GENERAL_MIRROR_SERVER@/idf/nghttp2.git ALLOW_TO_SYNC_FROM_PUBLIC +components/spiffs/spiffs @GENERAL_MIRROR_SERVER@/idf/spiffs.git ALLOW_TO_SYNC_FROM_PUBLIC third-party/mruby @GENERAL_MIRROR_SERVER@/idf/mruby.git ALLOW_TO_SYNC_FROM_PUBLIC third-party/neverbleed @GENERAL_MIRROR_SERVER@/idf/neverbleed.git ALLOW_TO_SYNC_FROM_PUBLIC diff --git a/tools/unit-test-app/partition_table_unit_test_app.csv b/tools/unit-test-app/partition_table_unit_test_app.csv index ea43d0791c..46173719b5 100644 --- a/tools/unit-test-app/partition_table_unit_test_app.csv +++ b/tools/unit-test-app/partition_table_unit_test_app.csv @@ -10,7 +10,5 @@ factory, 0, 0, 0x10000, 0x140000 # (done this way so tests can run in 2MB of flash.) ota_0, 0, ota_0, , 64K ota_1, 0, ota_1, , 64K -# flash_test partition used for SPI flash tests and WL FAT partition -# 528K is the minimal size needed to create a FAT partition -# (128 sectors for FAT + 4 sectors for WL) +# flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests flash_test, data, fat, , 528K