Merge branch 'feature/switch_to_new_vfs_api' into 'master'

feat(storage): switch to new VFS API

Closes IDF-11473

See merge request espressif/esp-idf!32029
This commit is contained in:
Tomas Rohlinek 2024-11-22 16:51:46 +08:00
commit 408ae4c560
18 changed files with 454 additions and 297 deletions

View File

@ -20,7 +20,7 @@ extern "C" {
*
* @return pointer to structure esp_vfs_t
*/
const esp_vfs_t *esp_vfs_uart_get_vfs(void);
const esp_vfs_fs_ops_t *esp_vfs_uart_get_vfs(void);
#ifdef __cplusplus
}

View File

@ -1032,8 +1032,29 @@ static int uart_tcflush(int fd, int select)
}
#endif // CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_t uart_vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
#ifdef CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_uart_dir = {
.access = &uart_access,
};
#endif // CONFIG_VFS_SUPPORT_DIR
#ifdef CONFIG_VFS_SUPPORT_SELECT
static const esp_vfs_select_ops_t s_vfs_uart_select = {
.start_select = &uart_start_select,
.end_select = &uart_end_select,
};
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_termios_ops_t s_vfs_uart_termios = {
.tcsetattr = &uart_tcsetattr,
.tcgetattr = &uart_tcgetattr,
.tcdrain = &uart_tcdrain,
.tcflush = &uart_tcflush,
};
#endif // CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_fs_ops_t s_vfs_uart = {
.write = &uart_write,
.open = &uart_open,
.fstat = &uart_fstat,
@ -1042,28 +1063,24 @@ static const esp_vfs_t uart_vfs = {
.fcntl = &uart_fcntl,
.fsync = &uart_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.access = &uart_access,
.dir = &s_vfs_uart_dir,
#endif // CONFIG_VFS_SUPPORT_DIR
#ifdef CONFIG_VFS_SUPPORT_SELECT
.start_select = &uart_start_select,
.end_select = &uart_end_select,
.select = &s_vfs_uart_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.tcsetattr = &uart_tcsetattr,
.tcgetattr = &uart_tcgetattr,
.tcdrain = &uart_tcdrain,
.tcflush = &uart_tcflush,
.termios = &s_vfs_uart_termios,
#endif // CONFIG_VFS_SUPPORT_TERMIOS
};
const esp_vfs_t *esp_vfs_uart_get_vfs(void)
const esp_vfs_fs_ops_t *esp_vfs_uart_get_vfs(void)
{
return &uart_vfs;
return &s_vfs_uart;
}
void uart_vfs_dev_register(void)
{
ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &uart_vfs, NULL));
ESP_ERROR_CHECK(esp_vfs_register_fs("/dev/uart", &s_vfs_uart, ESP_VFS_FLAG_STATIC, NULL));
}
int uart_vfs_dev_port_set_rx_line_endings(int uart_num, esp_line_endings_t mode)

View File

@ -20,7 +20,7 @@ extern "C" {
*
* @return pointer to structure esp_vfs_t
*/
const esp_vfs_t *esp_vfs_usb_serial_jtag_get_vfs(void);
const esp_vfs_fs_ops_t *esp_vfs_usb_serial_jtag_get_vfs(void);
#ifdef __cplusplus
}

View File

@ -571,8 +571,22 @@ void usb_serial_jtag_vfs_set_rx_line_endings(esp_line_endings_t mode)
s_ctx.rx_mode = mode;
}
static const esp_vfs_t usj_vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
#ifdef CONFIG_VFS_SUPPORT_SELECT
static const esp_vfs_select_ops_t s_vfs_jtag_select = {
.start_select = &usb_serial_jtag_start_select,
.end_select = &usb_serial_jtag_end_select,
};
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_termios_ops_t s_vfs_jtag_termios = {
.tcsetattr = &usb_serial_jtag_tcsetattr,
.tcgetattr = &usb_serial_jtag_tcgetattr,
.tcdrain = &usb_serial_jtag_tcdrain,
.tcflush = &usb_serial_jtag_tcflush,
};
#endif // CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_fs_ops_t s_vfs_jtag = {
.write = &usb_serial_jtag_write,
.open = &usb_serial_jtag_open,
.fstat = &usb_serial_jtag_fstat,
@ -580,27 +594,25 @@ static const esp_vfs_t usj_vfs = {
.read = &usb_serial_jtag_read,
.fcntl = &usb_serial_jtag_fcntl,
.fsync = &usb_serial_jtag_fsync,
#ifdef CONFIG_VFS_SUPPORT_SELECT
.start_select = &usb_serial_jtag_start_select,
.end_select = &usb_serial_jtag_end_select,
.select = &s_vfs_jtag_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.tcsetattr = &usb_serial_jtag_tcsetattr,
.tcgetattr = &usb_serial_jtag_tcgetattr,
.tcdrain = &usb_serial_jtag_tcdrain,
.tcflush = &usb_serial_jtag_tcflush,
.termios = &s_vfs_jtag_termios,
#endif // CONFIG_VFS_SUPPORT_TERMIOS
};
const esp_vfs_t* esp_vfs_usb_serial_jtag_get_vfs(void)
const esp_vfs_fs_ops_t* esp_vfs_usb_serial_jtag_get_vfs(void)
{
return &usj_vfs;
return &s_vfs_jtag;
}
esp_err_t usb_serial_jtag_vfs_register(void)
{
// "/dev/usb_serial_jtag" unfortunately is too long for vfs
return esp_vfs_register("/dev/usbserjtag", &usj_vfs, NULL);
return esp_vfs_register_fs("/dev/usbserjtag", &s_vfs_jtag, ESP_VFS_FLAG_STATIC, NULL);
}
#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
@ -615,7 +627,7 @@ ESP_SYSTEM_INIT_FN(init_vfs_usj, CORE, BIT(0), 111)
ESP_SYSTEM_INIT_FN(init_vfs_usj_sec, CORE, BIT(0), 112)
{
// "/dev/seccondary_usb_serial_jtag" unfortunately is too long for vfs
esp_vfs_register("/dev/secondary", &usj_vfs, NULL);
esp_vfs_register_fs("/dev/secondary", &s_vfs_jtag, ESP_VFS_FLAG_STATIC, NULL);
return ESP_OK;
}
#endif

View File

@ -773,8 +773,25 @@ static esp_err_t l2tap_end_select(void *end_select_args)
return ret;
}
static const esp_vfs_select_ops_t s_vfs_l2tap_select = {
.start_select = &l2tap_start_select,
.end_select = &l2tap_end_select,
};
#endif //CONFIG_VFS_SUPPORT_SELECT
static const esp_vfs_fs_ops_t s_vfs_l2tap = {
.write = &l2tap_write,
.open = &l2tap_open,
.close = &l2tap_close,
.read = &l2tap_read,
.fcntl = &l2tap_fcntl,
.ioctl = &l2tap_ioctl,
#ifdef CONFIG_VFS_SUPPORT_SELECT
.select = &s_vfs_l2tap_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
};
esp_err_t esp_vfs_l2tap_intf_register(l2tap_vfs_config_t *config)
{
l2tap_vfs_config_t def_config = L2TAP_VFS_CONFIG_DEFAULT();
@ -786,20 +803,7 @@ esp_err_t esp_vfs_l2tap_intf_register(l2tap_vfs_config_t *config)
ESP_RETURN_ON_FALSE(!s_is_registered, ESP_ERR_INVALID_STATE, TAG, "vfs is already registered");
s_is_registered = true;
esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.write = &l2tap_write,
.open = &l2tap_open,
.close = &l2tap_close,
.read = &l2tap_read,
.fcntl = &l2tap_fcntl,
.ioctl = &l2tap_ioctl,
#ifdef CONFIG_VFS_SUPPORT_SELECT
.start_select = &l2tap_start_select,
.end_select = &l2tap_end_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
};
ESP_RETURN_ON_ERROR(esp_vfs_register(config->base_path, &vfs, NULL), TAG, "vfs register error");
ESP_RETURN_ON_ERROR(esp_vfs_register_fs(config->base_path, &s_vfs_l2tap, ESP_VFS_FLAG_STATIC, NULL), TAG, "vfs register error");
return ESP_OK;
}

View File

@ -20,7 +20,7 @@ extern "C" {
*
* @return pointer to structure esp_vfs_t
*/
const esp_vfs_t *esp_vfs_cdcacm_get_vfs(void);
const esp_vfs_fs_ops_t *esp_vfs_cdcacm_get_vfs(void);
#ifdef __cplusplus
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -300,8 +300,7 @@ void esp_vfs_dev_cdcacm_set_rx_line_endings(esp_line_endings_t mode)
s_rx_mode = mode;
}
static const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
static const esp_vfs_fs_ops_t s_cdcacm_vfs = {
.write = &cdcacm_write,
.open = &cdcacm_open,
.fstat = &cdcacm_fstat,
@ -311,12 +310,12 @@ static const esp_vfs_t vfs = {
.fsync = &cdcacm_fsync
};
const esp_vfs_t *esp_vfs_cdcacm_get_vfs(void)
const esp_vfs_fs_ops_t *esp_vfs_cdcacm_get_vfs(void)
{
return &vfs;
return &s_cdcacm_vfs;
}
esp_err_t esp_vfs_dev_cdcacm_register(void)
{
return esp_vfs_register("/dev/cdcacm", &vfs, NULL);
return esp_vfs_register_fs("/dev/cdcacm", &s_cdcacm_vfs, ESP_VFS_FLAG_STATIC, NULL);
}

View File

@ -16,6 +16,7 @@
#include "sdkconfig.h"
#include "esp_private/startup_internal.h"
#include "esp_vfs_null.h"
#include "esp_private/nullfs.h"
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
@ -37,10 +38,10 @@ typedef struct {
// Secondary register part.
#if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG
const static esp_vfs_t *secondary_vfs = NULL;
const static esp_vfs_fs_ops_t *secondary_vfs = NULL;
#endif // Secondary part
const static esp_vfs_t *primary_vfs = NULL;
const static esp_vfs_fs_ops_t *primary_vfs = NULL;
static vfs_console_context_t vfs_console = {0};
@ -121,8 +122,8 @@ static esp_err_t console_start_select(int nfds, fd_set *readfds, fd_set *writefd
esp_vfs_select_sem_t select_sem, void **end_select_args)
{
// start_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig
if (primary_vfs->start_select) {
return primary_vfs->start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args);
if (primary_vfs->select->start_select) {
return primary_vfs->select->start_select(nfds, readfds, writefds, exceptfds, select_sem, end_select_args);
}
return ESP_ERR_NOT_SUPPORTED;
@ -131,8 +132,8 @@ static esp_err_t console_start_select(int nfds, fd_set *readfds, fd_set *writefd
esp_err_t console_end_select(void *end_select_args)
{
// end_select is not guaranteed be implemented even though CONFIG_VFS_SUPPORT_SELECT is enabled in sdkconfig
if (primary_vfs->end_select) {
return primary_vfs->end_select(end_select_args);
if (primary_vfs->select->end_select) {
return primary_vfs->select->end_select(end_select_args);
}
return ESP_ERR_NOT_SUPPORTED;
@ -163,8 +164,29 @@ int console_tcflush(int fd, int select)
}
#endif // CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
#ifdef CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_console_dir = {
.access = &console_access,
};
#endif // CONFIG_VFS_SUPPORT_DIR
#ifdef CONFIG_VFS_SUPPORT_SELECT
static const esp_vfs_select_ops_t s_vfs_console_select = {
.start_select = &console_start_select,
.end_select = &console_end_select,
};
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_termios_ops_t s_vfs_console_termios = {
.tcsetattr = &console_tcsetattr,
.tcgetattr = &console_tcgetattr,
.tcdrain = &console_tcdrain,
.tcflush = &console_tcflush,
};
#endif // CONFIG_VFS_SUPPORT_TERMIOS
static const esp_vfs_fs_ops_t s_vfs_console = {
.write = &console_write,
.open = &console_open,
.fstat = &console_fstat,
@ -172,24 +194,23 @@ static const esp_vfs_t vfs = {
.read = &console_read,
.fcntl = &console_fcntl,
.fsync = &console_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.access = &console_access,
.dir = &s_vfs_console_dir,
#endif // CONFIG_VFS_SUPPORT_DIR
#ifdef CONFIG_VFS_SUPPORT_SELECT
.start_select = &console_start_select,
.end_select = &console_end_select,
.select = &s_vfs_console_select,
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.tcsetattr = &console_tcsetattr,
.tcgetattr = &console_tcgetattr,
.tcdrain = &console_tcdrain,
.tcflush = &console_tcflush,
.termios = &s_vfs_console_termios,
#endif // CONFIG_VFS_SUPPORT_TERMIOS
};
static esp_err_t esp_vfs_dev_console_register(void)
{
return esp_vfs_register(ESP_VFS_DEV_CONSOLE, &vfs, NULL);
return esp_vfs_register_fs(ESP_VFS_DEV_CONSOLE, &s_vfs_console, ESP_VFS_FLAG_STATIC, NULL);
}
esp_err_t esp_vfs_console_register(void)

View File

@ -140,6 +140,42 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz
return esp_vfs_fat_register_cfg(&conf, out_fs);
}
#ifdef CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_fat_dir = {
.stat_p = &vfs_fat_stat,
.link_p = &vfs_fat_link,
.unlink_p = &vfs_fat_unlink,
.rename_p = &vfs_fat_rename,
.opendir_p = &vfs_fat_opendir,
.closedir_p = &vfs_fat_closedir,
.readdir_p = &vfs_fat_readdir,
.readdir_r_p = &vfs_fat_readdir_r,
.seekdir_p = &vfs_fat_seekdir,
.telldir_p = &vfs_fat_telldir,
.mkdir_p = &vfs_fat_mkdir,
.rmdir_p = &vfs_fat_rmdir,
.access_p = &vfs_fat_access,
.truncate_p = &vfs_fat_truncate,
.ftruncate_p = &vfs_fat_ftruncate,
.utime_p = &vfs_fat_utime,
};
#endif // CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_fs_ops_t s_vfs_fat = {
.write_p = &vfs_fat_write,
.lseek_p = &vfs_fat_lseek,
.read_p = &vfs_fat_read,
.pread_p = &vfs_fat_pread,
.pwrite_p = &vfs_fat_pwrite,
.open_p = &vfs_fat_open,
.close_p = &vfs_fat_close,
.fstat_p = &vfs_fat_fstat,
.fsync_p = &vfs_fat_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = &s_vfs_fat_dir,
#endif // CONFIG_VFS_SUPPORT_DIR
};
esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_fs)
{
size_t ctx = find_context_index_by_path(conf->base_path);
@ -152,37 +188,6 @@ esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_f
return ESP_ERR_NO_MEM;
}
const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &vfs_fat_write,
.lseek_p = &vfs_fat_lseek,
.read_p = &vfs_fat_read,
.pread_p = &vfs_fat_pread,
.pwrite_p = &vfs_fat_pwrite,
.open_p = &vfs_fat_open,
.close_p = &vfs_fat_close,
.fstat_p = &vfs_fat_fstat,
.fsync_p = &vfs_fat_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.stat_p = &vfs_fat_stat,
.link_p = &vfs_fat_link,
.unlink_p = &vfs_fat_unlink,
.rename_p = &vfs_fat_rename,
.opendir_p = &vfs_fat_opendir,
.closedir_p = &vfs_fat_closedir,
.readdir_p = &vfs_fat_readdir,
.readdir_r_p = &vfs_fat_readdir_r,
.seekdir_p = &vfs_fat_seekdir,
.telldir_p = &vfs_fat_telldir,
.mkdir_p = &vfs_fat_mkdir,
.rmdir_p = &vfs_fat_rmdir,
.access_p = &vfs_fat_access,
.truncate_p = &vfs_fat_truncate,
.ftruncate_p = &vfs_fat_ftruncate,
.utime_p = &vfs_fat_utime,
#endif // CONFIG_VFS_SUPPORT_DIR
};
size_t max_files = conf->max_files;
if (max_files < 1) {
max_files = 1; // ff_memalloc(max_files * sizeof(bool)) below will fail if max_files == 0
@ -204,7 +209,7 @@ esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_f
strlcpy(fat_ctx->fat_drive, conf->fat_drive, sizeof(fat_ctx->fat_drive) - 1);
strlcpy(fat_ctx->base_path, conf->base_path, sizeof(fat_ctx->base_path) - 1);
esp_err_t err = esp_vfs_register(conf->base_path, &vfs, fat_ctx);
esp_err_t err = esp_vfs_register_fs(conf->base_path, &s_vfs_fat, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, fat_ctx);
if (err != ESP_OK) {
free(fat_ctx->o_append);
free(fat_ctx);

View File

@ -399,6 +399,43 @@ esp_err_t esp_spiffs_gc(const char* partition_label, size_t size_to_gc)
return ESP_OK;
}
#ifdef CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_spiffs_dir = {
.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,
.truncate_p = &vfs_spiffs_truncate,
.ftruncate_p = &vfs_spiffs_ftruncate,
#ifdef CONFIG_SPIFFS_USE_MTIME
.utime_p = &vfs_spiffs_utime,
#else
.utime_p = NULL,
#endif // CONFIG_SPIFFS_USE_MTIME
};
#endif // CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_fs_ops_t s_vfs_spiffs = {
.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,
.fsync_p = &vfs_spiffs_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = &s_vfs_spiffs_dir,
#endif // CONFIG_VFS_SUPPORT_DIR
};
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)
{
assert(conf->base_path);
@ -413,45 +450,13 @@ esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)
return ESP_ERR_INVALID_STATE;
}
int vfs_flags = ESP_VFS_FLAG_CONTEXT_PTR;
int vfs_flags = ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC;
if (_efs[index]->partition->readonly) {
vfs_flags |= ESP_VFS_FLAG_READONLY_FS;
}
const esp_vfs_t vfs = {
.flags = vfs_flags,
.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,
.fsync_p = &vfs_spiffs_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.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,
.truncate_p = &vfs_spiffs_truncate,
.ftruncate_p = &vfs_spiffs_ftruncate,
#ifdef CONFIG_SPIFFS_USE_MTIME
.utime_p = &vfs_spiffs_utime,
#else
.utime_p = NULL,
#endif // CONFIG_SPIFFS_USE_MTIME
#endif // CONFIG_VFS_SUPPORT_DIR
};
strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1);
err = esp_vfs_register(conf->base_path, &vfs, _efs[index]);
err = esp_vfs_register_fs(conf->base_path, &s_vfs_spiffs, vfs_flags, _efs[index]);
if (err != ESP_OK) {
esp_spiffs_free(&_efs[index]);
return err;

View File

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_vfs.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get VFS structure for /dev/null
*
* @return VFS structure for /dev/null
*/
const esp_vfs_fs_ops_t *esp_vfs_null_get_vfs(void);
#ifdef __cplusplus
}
#endif

View File

@ -12,13 +12,6 @@
extern "C" {
#endif
/**
* @brief Get VFS structure for /dev/null
*
* @return VFS structure for /dev/null
*/
const esp_vfs_t *esp_vfs_null_get_vfs(void);
/**
* @brief Register filesystem for /dev/null
*

View File

@ -243,15 +243,15 @@ typedef struct {
};
#ifdef CONFIG_VFS_SUPPORT_DIR
esp_vfs_dir_ops_t *dir; /*!< pointer to the dir subcomponent */
const esp_vfs_dir_ops_t *dir; /*!< pointer to the dir subcomponent */
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
esp_vfs_termios_ops_t *termios; /*!< pointer to the termios subcomponent */
const esp_vfs_termios_ops_t *termios; /*!< pointer to the termios subcomponent */
#endif
#if CONFIG_VFS_SUPPORT_SELECT || defined __DOXYGEN__
esp_vfs_select_ops_t *select; /*!< pointer to the select subcomponent */
const esp_vfs_select_ops_t *select; /*!< pointer to the select subcomponent */
#endif
} esp_vfs_fs_ops_t;

View File

@ -16,6 +16,7 @@
#include <sys/types.h>
#include <fcntl.h>
#include "esp_vfs.h"
#include "esp_vfs_ops.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_private/startup_internal.h"
@ -68,8 +69,13 @@ static int vfs_null_fcntl(int fd, int cmd, int arg);
static int vfs_null_ioctl(int fd, int cmd, va_list args);
static int vfs_null_fsync(int fd);
static const esp_vfs_t s_vfs_null = {
.flags = ESP_VFS_FLAG_DEFAULT,
#if CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_null_dir = {
.stat = &vfs_null_stat,
};
#endif // CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_fs_ops_t s_vfs_null = {
.write = &vfs_null_write,
.lseek = &vfs_null_lseek,
.read = &vfs_null_read,
@ -78,22 +84,22 @@ static const esp_vfs_t s_vfs_null = {
.open = &vfs_null_open,
.close = &vfs_null_close,
.fstat = &vfs_null_fstat,
#if CONFIG_VFS_SUPPORT_DIR
.stat = &vfs_null_stat,
#endif // CONFIG_VFS_SUPPORT_DIR
.fcntl = &vfs_null_fcntl,
.ioctl = &vfs_null_ioctl,
.fsync = &vfs_null_fsync,
#if CONFIG_VFS_SUPPORT_DIR
.dir = &s_vfs_null_dir,
#endif // CONFIG_VFS_SUPPORT_DIR
};
const esp_vfs_t *esp_vfs_null_get_vfs(void)
const esp_vfs_fs_ops_t *esp_vfs_null_get_vfs(void)
{
return &s_vfs_null;
}
esp_err_t esp_vfs_null_register(void)
{
return esp_vfs_register("/dev/null", &s_vfs_null, NULL);
return esp_vfs_register_fs("/dev/null", &s_vfs_null, ESP_VFS_FLAG_STATIC, NULL);
}
static ssize_t vfs_null_write(int fd, const void *data, size_t size)

View File

@ -86,16 +86,17 @@ static ssize_t esp_get_free_index(void) {
}
static void esp_vfs_free_fs_ops(esp_vfs_fs_ops_t *vfs) {
// We can afford to cast away the const qualifier here, because we know that we allocated the struct and therefore its safe
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
free(vfs->termios);
free((void*)vfs->termios);
#endif
#ifdef CONFIG_VFS_SUPPORT_DIR
free(vfs->dir);
free((void*)vfs->dir);
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
free(vfs->select);
free((void*)vfs->select);
#endif
free(vfs);
@ -113,36 +114,37 @@ static void esp_vfs_free_entry(vfs_entry_t *entry) {
free(entry);
}
static void esp_minify_vfs(const esp_vfs_t * const vfs, esp_vfs_fs_ops_t *min) {
assert(vfs != NULL);
assert(min != NULL);
*min = (esp_vfs_fs_ops_t) {
.write = vfs->write,
.lseek = vfs->lseek,
.read = vfs->read,
.pread = vfs->pread,
.pwrite = vfs->pwrite,
.open = vfs->open,
.close = vfs->close,
.fstat = vfs->fstat,
.fcntl = vfs->fcntl,
.ioctl = vfs->ioctl,
.fsync = vfs->fsync,
typedef struct {
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = min->dir,
esp_vfs_dir_ops_t *dir;
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.termios = min->termios,
esp_vfs_termios_ops_t *termios;
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
.select = min->select,
esp_vfs_select_ops_t *select;
#endif
};
} vfs_component_proxy_t;
static void free_proxy_members(vfs_component_proxy_t *proxy) {
#ifdef CONFIG_VFS_SUPPORT_DIR
free(proxy->dir);
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
free(proxy->termios);
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
free(proxy->select);
#endif
}
static esp_vfs_fs_ops_t *esp_minify_vfs(const esp_vfs_t * const vfs, vfs_component_proxy_t proxy) {
assert(vfs != NULL);
#ifdef CONFIG_VFS_SUPPORT_DIR
// If the dir functions are not implemented, we don't need to convert them
if (min->dir != NULL) {
*(min->dir) = (esp_vfs_dir_ops_t) {
if (proxy.dir != NULL) {
*(proxy.dir) = (esp_vfs_dir_ops_t) {
.stat = vfs->stat,
.link = vfs->link,
.unlink = vfs->unlink,
@ -165,8 +167,8 @@ static void esp_minify_vfs(const esp_vfs_t * const vfs, esp_vfs_fs_ops_t *min) {
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
// If the termios functions are not implemented, we don't need to convert them
if (min->termios != NULL) {
*(min->termios) = (esp_vfs_termios_ops_t) {
if (proxy.termios != NULL) {
*(proxy.termios) = (esp_vfs_termios_ops_t) {
.tcsetattr = vfs->tcsetattr,
.tcgetattr = vfs->tcgetattr,
.tcdrain = vfs->tcdrain,
@ -180,8 +182,8 @@ static void esp_minify_vfs(const esp_vfs_t * const vfs, esp_vfs_fs_ops_t *min) {
#ifdef CONFIG_VFS_SUPPORT_SELECT
// If the select functions are not implemented, we don't need to convert them
if (min->select != NULL) {
*(min->select) = (esp_vfs_select_ops_t) {
if (proxy.select != NULL) {
*(proxy.select) = (esp_vfs_select_ops_t) {
.start_select = vfs->start_select,
.socket_select = vfs->socket_select,
.stop_socket_select = vfs->stop_socket_select,
@ -192,63 +194,109 @@ static void esp_minify_vfs(const esp_vfs_t * const vfs, esp_vfs_fs_ops_t *min) {
}
#endif // CONFIG_VFS_SUPPORT_SELECT
}
esp_vfs_fs_ops_t tmp = {
.write = vfs->write,
.lseek = vfs->lseek,
.read = vfs->read,
.pread = vfs->pread,
.pwrite = vfs->pwrite,
.open = vfs->open,
.close = vfs->close,
.fstat = vfs->fstat,
.fcntl = vfs->fcntl,
.ioctl = vfs->ioctl,
.fsync = vfs->fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = proxy.dir,
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.termios = proxy.termios,
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
.select = proxy.select,
#endif
};
static esp_vfs_fs_ops_t* esp_vfs_duplicate_fs_ops(const esp_vfs_fs_ops_t *vfs) {
esp_vfs_fs_ops_t *min = (esp_vfs_fs_ops_t*) heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS);
if (min == NULL) {
esp_vfs_fs_ops_t *out = heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS);
if (out == NULL) {
return NULL;
}
memcpy(min, vfs, sizeof(esp_vfs_fs_ops_t));
// Doing this is the only way to correctly initialize const members of a struct according to C standard
memcpy(out, &tmp, sizeof(esp_vfs_fs_ops_t));
// remove references to the original components
#ifdef CONFIG_VFS_SUPPORT_DIR
min->dir = NULL;
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
min->termios = NULL;
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
min->select = NULL;
#endif
return out;
}
static esp_vfs_fs_ops_t* esp_vfs_duplicate_fs_ops(const esp_vfs_fs_ops_t *orig) {
vfs_component_proxy_t proxy = {};
#ifdef CONFIG_VFS_SUPPORT_DIR
if (vfs->dir != NULL) {
min->dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS);
if (min->dir == NULL) {
if (orig->dir != NULL) {
proxy.dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS);
if (proxy.dir == NULL) {
goto fail;
}
memcpy(min->dir, vfs->dir, sizeof(esp_vfs_dir_ops_t));
memcpy(proxy.dir, orig->dir, sizeof(esp_vfs_dir_ops_t));
}
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
if (vfs->termios != NULL) {
min->termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS);
if (min->termios == NULL) {
if (orig->termios != NULL) {
proxy.termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS);
if (proxy.termios == NULL) {
goto fail;
}
memcpy(min->termios, vfs->termios, sizeof(esp_vfs_termios_ops_t));
memcpy(proxy.termios, orig->termios, sizeof(esp_vfs_termios_ops_t));
}
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
if (vfs->select != NULL) {
min->select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS);
if (min->select == NULL) {
if (orig->select != NULL) {
proxy.select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS);
if (proxy.select == NULL) {
goto fail;
}
memcpy(min->select, vfs->select, sizeof(esp_vfs_select_ops_t));
memcpy(proxy.select, orig->select, sizeof(esp_vfs_select_ops_t));
}
#endif
return min;
// This tediousness is required because of const members
esp_vfs_fs_ops_t tmp = {
.write = orig->write,
.lseek = orig->lseek,
.read = orig->read,
.pread = orig->pread,
.pwrite = orig->pwrite,
.open = orig->open,
.close = orig->close,
.fstat = orig->fstat,
.fcntl = orig->fcntl,
.ioctl = orig->ioctl,
.fsync = orig->fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = proxy.dir,
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
.termios = proxy.termios,
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
.select = proxy.select,
#endif
};
esp_vfs_fs_ops_t *out = heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS);
if (out == NULL) {
goto fail;
}
memcpy(out, &tmp, sizeof(esp_vfs_fs_ops_t));
return out;
#if defined(CONFIG_VFS_SUPPORT_SELECT) || defined(CONFIG_VFS_SUPPORT_TERMIOS) || defined(CONFIG_VFS_SUPPORT_DIR)
fail:
#endif
esp_vfs_free_fs_ops(min);
free_proxy_members(&proxy);
return NULL;
}
@ -263,16 +311,10 @@ static esp_err_t esp_vfs_make_fs_ops(const esp_vfs_t *vfs, esp_vfs_fs_ops_t **mi
return ESP_ERR_INVALID_ARG;
}
esp_vfs_fs_ops_t *main = (esp_vfs_fs_ops_t*) heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS);
if (main == NULL) {
return ESP_ERR_NO_MEM;
}
// Initialize all fields to NULL
memset(main, 0, sizeof(esp_vfs_fs_ops_t));
vfs_component_proxy_t proxy = {};
#ifdef CONFIG_VFS_SUPPORT_DIR
bool skip_dir =
const bool skip_dir =
vfs->stat == NULL &&
vfs->link == NULL &&
vfs->unlink == NULL &&
@ -291,15 +333,15 @@ static esp_err_t esp_vfs_make_fs_ops(const esp_vfs_t *vfs, esp_vfs_fs_ops_t **mi
vfs->utime == NULL;
if (!skip_dir) {
main->dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS);
if (main->dir == NULL) {
proxy.dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS);
if (proxy.dir == NULL) {
goto fail;
}
}
#endif
#ifdef CONFIG_VFS_SUPPORT_TERMIOS
bool skip_termios =
const bool skip_termios =
vfs->tcsetattr == NULL &&
vfs->tcgetattr == NULL &&
vfs->tcdrain == NULL &&
@ -309,15 +351,15 @@ static esp_err_t esp_vfs_make_fs_ops(const esp_vfs_t *vfs, esp_vfs_fs_ops_t **mi
vfs->tcsendbreak == NULL;
if (!skip_termios) {
main->termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS);
if (main->termios == NULL) {
proxy.termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS);
if (proxy.termios == NULL) {
goto fail;
}
}
#endif
#ifdef CONFIG_VFS_SUPPORT_SELECT
bool skip_select =
const bool skip_select =
vfs->start_select == NULL &&
vfs->socket_select == NULL &&
vfs->stop_socket_select == NULL &&
@ -326,24 +368,25 @@ static esp_err_t esp_vfs_make_fs_ops(const esp_vfs_t *vfs, esp_vfs_fs_ops_t **mi
vfs->end_select == NULL;
if (!skip_select) {
main->select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS);
if (main->select == NULL) {
proxy.select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS);
if (proxy.select == NULL) {
goto fail;
}
}
#endif
esp_minify_vfs(vfs, main);
esp_vfs_fs_ops_t *main = esp_minify_vfs(vfs, proxy);
if (main == NULL) {
goto fail;
}
*min = main;
return ESP_OK;
#if defined(CONFIG_VFS_SUPPORT_SELECT) || defined(CONFIG_VFS_SUPPORT_TERMIOS) || defined(CONFIG_VFS_SUPPORT_DIR)
fail:
esp_vfs_free_fs_ops(main);
free_proxy_members(&proxy);
return ESP_ERR_NO_MEM;
#endif
}
static esp_err_t esp_vfs_register_fs_common(const char* base_path, size_t len, const esp_vfs_fs_ops_t* vfs, int flags, void* ctx, int *vfs_index)

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -13,6 +13,7 @@
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_cpu.h"
#include "include/esp_vfs.h"
#include "openocd_semihosting.h"
#ifndef CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS
@ -419,37 +420,44 @@ static void vfs_semihost_seekdir(void* ctx, DIR* pdir, long offset)
}
}
#endif
#ifdef CONFIG_VFS_SUPPORT_DIR
static const esp_vfs_dir_ops_t s_vfs_semihost_dir = {
.mkdir_p = &vfs_semihost_mkdir,
.rmdir_p = &vfs_semihost_rmdir,
.access_p = &vfs_semihost_access,
.truncate_p = &vfs_semihost_truncate,
.utime_p = &vfs_semihost_utime,
.stat_p = &vfs_semihost_stat,
.rename_p = &vfs_semihost_rename,
.link_p = &vfs_semihost_link,
.unlink_p = &vfs_semihost_unlink,
.opendir_p = &vfs_semihost_opendir,
.closedir_p = &vfs_semihost_closedir,
.telldir_p = &vfs_semihost_telldir,
.readdir_p = &vfs_semihost_readdir,
.readdir_r_p = &vfs_semihost_readdir_r,
.seekdir_p = &vfs_semihost_seekdir,
};
#endif
static const esp_vfs_fs_ops_t s_vfs_semihost = {
.write_p = &vfs_semihost_write,
.open_p = &vfs_semihost_open,
.close_p = &vfs_semihost_close,
.read_p = &vfs_semihost_read,
.lseek_p = &vfs_semihost_lseek,
.fsync_p = &vfs_semihost_fsync,
.fstat_p = &vfs_semihost_fstat,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = &s_vfs_semihost_dir,
#endif
};
esp_err_t esp_vfs_semihost_register(const char* base_path)
{
assert(base_path);
const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &vfs_semihost_write,
.open_p = &vfs_semihost_open,
.close_p = &vfs_semihost_close,
.read_p = &vfs_semihost_read,
.lseek_p = &vfs_semihost_lseek,
.fsync_p = &vfs_semihost_fsync,
.fstat_p = &vfs_semihost_fstat,
#ifdef CONFIG_VFS_SUPPORT_DIR
.mkdir_p = &vfs_semihost_mkdir,
.rmdir_p = &vfs_semihost_rmdir,
.access_p = &vfs_semihost_access,
.truncate_p = &vfs_semihost_truncate,
.utime_p = &vfs_semihost_utime,
.stat_p = &vfs_semihost_stat,
.rename_p = &vfs_semihost_rename,
.link_p = &vfs_semihost_link,
.unlink_p = &vfs_semihost_unlink,
.opendir_p = &vfs_semihost_opendir,
.closedir_p = &vfs_semihost_closedir,
.telldir_p = &vfs_semihost_telldir,
.readdir_p = &vfs_semihost_readdir,
.readdir_r_p = &vfs_semihost_readdir_r,
.seekdir_p = &vfs_semihost_seekdir,
#endif
};
ESP_LOGD(TAG, "Register semihosting driver '%s'", base_path);
if (!esp_cpu_dbgr_is_attached()) {
ESP_LOGE(TAG, "OpenOCD is not connected!");
@ -478,7 +486,7 @@ esp_err_t esp_vfs_semihost_register(const char* base_path)
ESP_LOGE(TAG, "Incompatible OpenOCD version detected. Please follow the getting started guides to install the required version.");
}
err = esp_vfs_register(base_path, &vfs, &s_semhost_ctx[i]);
err = esp_vfs_register_fs(base_path, &s_vfs_semihost, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, &s_semhost_ctx[i]);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Can't register the semihosting! Error: %s", esp_err_to_name(err));
return err;

View File

@ -137,7 +137,7 @@ To send application output to a custom channel (for example, a WebSocket connect
- ``fstat()`` — recommended, to provide correct buffering behavior for the I/O streams
- ``fcntl()`` — only if non-blocking I/O has to be supported
Once you have created a custom VFS driver, use ``esp_vfs_register()`` to register it with VFS. Then, use ``fopen()`` to redirect ``stdout`` and ``stderr`` to the custom channel. For example:
Once you have created a custom VFS driver, use :cpp:func:`esp_vfs_register_fs()` to register it with VFS. Then, use ``fopen()`` to redirect ``stdout`` and ``stderr`` to the custom channel. For example:
.. code-block:: c

View File

@ -3,70 +3,96 @@ Virtual Filesystem Component
:link_to_translation:`zh_CN:[中文]`
Overview
--------
Virtual filesystem (VFS) component provides a unified interface for drivers which can perform operations on file-like objects. These can be real filesystems (FAT, SPIFFS, etc.) or device drivers which provide a file-like interface.
This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At a high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, the VFS component searches for the FS driver associated with the file path and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver.
This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At a high level, each FS driver is mounted at some path prefix. When one of C library functions needs to open a file, the VFS component searches for the FS driver associated with the file path and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver.
For example, one can register a FAT filesystem driver with the ``/fat`` prefix and call ``fopen("/fat/file.txt", "w")``. Then the VFS component calls the function ``open`` of the FAT driver and pass the argument ``/file.txt`` to it together with appropriate mode flags. All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver.
For example, one can mount a FAT filesystem driver at the ``/fat`` prefix and call ``fopen("/fat/file.txt", "w")``. Then the VFS component calls the function ``open`` of the FAT driver and pass the argument ``/file.txt`` to it together with appropriate mode flags. All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver.
FS Registration
---------------
To register an FS driver, an application needs to define an instance of the :cpp:type:`esp_vfs_t` structure and populate it with function pointers to FS APIs:
.. note:: For previous version of the API (using :cpp:type:`esp_vfs_t`) see documentation for previous release.
To register an FS driver, an application needs to define an instance of the :cpp:type:`esp_vfs_fs_ops_t` structure and populate it with function pointers to FS APIs:
.. highlight:: c
::
esp_vfs_t myfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.write = &myfs_write,
.open = &myfs_open,
// Both esp_vfs_fs_ops_t and its subcomponents have to have static storage
static const esp_vfs_dir_ops_t myfs_dir = {
.fstat = &myfs_fstat,
.close = &myfs_close,
.read = &myfs_read,
};
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
Depending on the way how the FS driver declares its API functions, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc., should be used.
Case 1: API functions are declared without an extra context pointer (the FS driver is a singleton)::
ssize_t myfs_write(int fd, const void * data, size_t size);
// In definition of esp_vfs_t:
.flags = ESP_VFS_FLAG_DEFAULT,
static const esp_vfs_fs_ops_t myfs = {
.write = &myfs_write,
// ... other members initialized
.open = &myfs_open,
.close = &myfs_close,
.read = &myfs_read,
.dir = &myfs_dir,
};
// When registering FS, context pointer (the third argument) is NULL:
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_STATIC, NULL));
Case 2: API functions are declared with an extra context pointer (the FS driver supports multiple instances)::
Non-static :cpp:type:`esp_vfs_fs_ops_t`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The recommended approach for registering filesystem is to use statically allocated :cpp:type:`esp_vfs_fs_ops_t` alongside ``ESP_VFS_FLAG_STATIC``, as it is more memory efficient. In cases where using static allocation is not possible, ``ESP_VFS_FLAG_STATIC`` can replaced with ``ESP_VFS_FLAG_DEFAULT``. This tells VFS to make a deep copy of the passed structure in RAM, this copy will be managed by VFS component.
.. highlight:: c
::
// Possibly local scope
{
esp_vfs_dir_ops_t myfs_dir = {
.fstat = &myfs_fstat,
};
bool some_condition = false;
esp_vfs_fs_ops_t myfs = {
.write = some_condition ? &myfs_special_write : &myfs_write,
// ... other members
.dir = &myfs_dir,
};
ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_DEFAULT, NULL));
}
Context aware filesystem
^^^^^^^^^^^^^^^^^^^^^^^^
In some cases it might be beneficial, or even necessary to pass some context to the filesystem functions, such as mountpoint specific file descriptor table, when multiple instances of FS mounted. For this reason the :cpp:type:`esp_vfs_fs_ops_t` contains second version of each member with ``_p`` suffix, for example for ``read`` there is ``read_p``, these functions have additional first argument. When registering the FS, ``ESP_VFS_FLAG_CONTEXT_PTR`` needs to be specified and the pointer passed as the last argument.
::
ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);
// In definition of esp_vfs_t:
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &myfs_write,
// ... other members initialized
// When registering FS, pass the FS context pointer into the third argument
// When registering FS, pass the ESP_VFS_FLAG_CONTEXT_PTR flag, alongside FS context pointer into the third argument
// (hypothetical myfs_mount function is used for illustrative purposes)
myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data1", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst1));
// Can register another instance:
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data2", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst2));
Synchronous Input/Output Multiplexing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-------------------------------------
Synchronous input/output multiplexing by :cpp:func:`select` is supported in the VFS component. The implementation works in the following way.
@ -82,8 +108,9 @@ Synchronous input/output multiplexing by :cpp:func:`select` is supported in the
6. The :cpp:func:`select` call ends and returns the appropriate results.
Non-Socket VFS Drivers
""""""""""""""""""""""
^^^^^^^^^^^^^^^^^^^^^^
If you want to use :cpp:func:`select` with a file descriptor belonging to a non-socket VFS driver, then you need to register the driver with functions :cpp:func:`start_select` and :cpp:func:`end_select` similarly to the following example:
@ -91,7 +118,7 @@ If you want to use :cpp:func:`select` with a file descriptor belonging to a non-
::
// In definition of esp_vfs_t:
// In definition of esp_vfs_select_ops_t:
.start_select = &uart_start_select,
.end_select = &uart_end_select,
// ... other members initialized
@ -113,7 +140,7 @@ Please check the following examples that demonstrate the use of :cpp:func:`selec
Socket VFS Drivers
""""""""""""""""""
^^^^^^^^^^^^^^^^^^
A socket VFS driver is using its own internal implementation of :cpp:func:`select` and non-socket VFS drivers notify it upon read/write/error conditions.
@ -123,7 +150,7 @@ A socket VFS driver needs to be registered with the following functions defined:
::
// In definition of esp_vfs_t:
// In definition of esp_vfs_select_ops_t:
.socket_select = &lwip_select,
.get_socket_select_semaphore = &lwip_get_socket_select_semaphore,
.stop_socket_select = &lwip_stop_socket_select,
@ -146,6 +173,7 @@ Please see :component_file:`lwip/port/esp32xx/vfs_lwip.c` for a reference socket
You should not change the socket driver during an active :cpp:func:`select` call or you might experience some undefined behavior.
Paths
-----
@ -195,16 +223,6 @@ Standard I/O streams (``stdin``, ``stdout``, ``stderr``) are mapped to file desc
Note that creating an eventfd with ``EFD_SUPPORT_ISR`` will cause interrupts to be temporarily disabled when reading, writing the file and during the beginning and the ending of the ``select()`` when this file is set.
Minified VFS
------------
To minimize RAM usage, an alternative version of :cpp:func:`esp_vfs_register` function, :cpp:func:`esp_vfs_register_fs` is provided. This version accepts :cpp:class:`esp_vfs_fs_ops_t` instead of :cpp:class:`esp_vfs_t` alongside separate argument for OR-ed flags. Unlike :cpp:func:`esp_vfs_register`, it can handle statically allocated struct, as long as the ``ESP_VFS_FLAG_STATIC`` is provided.
The :cpp:class:`esp_vfs_fs_ops_t` is split into separate structs based on features (directory operations, select support, termios support, ...). The main struct contains the basic functions (``read``, ``write``, ...), alongside pointers to the feature-specific structs. These pointers can be ``NULL`` indicating lack of support for all the functions provided by that struct, which decreases the required memory.
Internally the VFS component uses this version of API, with additional steps to convert the :cpp:class:`esp_vfs_t` to :cpp:class:`esp_vfs_fs_ops_t` upon registration.
Well Known VFS Devices
----------------------
@ -214,6 +232,7 @@ IDF defines several VFS devices that can be used by applications. These devices
* ``/dev/null`` - file that discards all data written to it and returns EOF when read. It is automatically created if :ref:`CONFIG_VFS_INITIALIZE_DEV_NULL` is enabled.
* ``/dev/console`` - file that is connected to the primary and secondary outputs specified in the menuconfig by :ref:`CONFIG_ESP_CONSOLE_UART` and :ref:`CONFIG_ESP_CONSOLE_SECONDARY` respectively. More information can be found here :doc:`../../api-guides/stdio`.
Application Examples
--------------------
@ -223,6 +242,7 @@ Application Examples
- :example:`storage/semihost_vfs` demonstrates how to use the semihosting VFS driver, including registering a host directory, redirecting stdout from UART to a file on the host, and reading and printing the content of a text file.
API Reference
-------------