VFS: Use smaller numbers as file descriptors

This commit is contained in:
Roland Dobai 2018-05-07 09:01:56 +02:00
parent dc13f489ca
commit 5129bca67c
12 changed files with 454 additions and 176 deletions

View File

@ -890,6 +890,13 @@ UT_004_09:
- UT_T1_1
- UT_psram
UT_004_10:
<<: *unit_test_template
tags:
- ESP32_IDF
- UT_T1_1
- UT_psram
IT_001_01:
<<: *test_template
tags:

View File

@ -16,5 +16,5 @@
#define IDF_PERFORMANCE_MAX_ESP_TIMER_GET_TIME_PER_CALL 1000
#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 30
#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 27
#define IDF_PERFORMANCE_MAX_VFS_OPEN_WRITE_CLOSE_TIME 20000

View File

@ -446,8 +446,6 @@ typedef struct fd_set
unsigned char fd_bits [(FD_SETSIZE+7)/8];
} fd_set;
#elif LWIP_SOCKET_OFFSET
#error LWIP_SOCKET_OFFSET does not work with external FD_SET!
#endif /* FD_SET */
/** LWIP_TIMEVAL_PRIVATE: if you want to use the struct timeval provided

View File

@ -16,11 +16,6 @@
extern "C" {
#endif
/* Internal declarations used to ingreate LWIP port layer
to ESP-IDF VFS for POSIX I/O.
*/
extern int lwip_socket_offset;
void esp_vfs_lwip_sockets_register();
#ifdef __cplusplus

View File

@ -38,6 +38,7 @@
#include <sys/time.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include "esp_task.h"
#include "esp_system.h"
#include "sdkconfig.h"
@ -698,12 +699,15 @@
*/
#define LWIP_POSIX_SOCKETS_IO_NAMES 0
/**
* Socket offset is also determined via the VFS layer at
* filesystem registration time (see port/vfs_lwip.c)
* FD_SETSIZE from sys/types.h is the maximum number of supported file
* descriptors and CONFIG_LWIP_MAX_SOCKETS defines the number of sockets;
* LWIP_SOCKET_OFFSET is configured to use the largest numbers of file
* descriptors for sockets. File descriptors from 0 to LWIP_SOCKET_OFFSET-1
* are non-socket descriptors and from LWIP_SOCKET_OFFSET to FD_SETSIZE are
* socket descriptors.
*/
#define LWIP_SOCKET_OFFSET lwip_socket_offset
#define LWIP_SOCKET_OFFSET (FD_SETSIZE - CONFIG_LWIP_MAX_SOCKETS)
/* Enable all Espressif-only options */

View File

@ -25,19 +25,11 @@
#include "lwip/sockets.h"
#include "sdkconfig.h"
/* LWIP is a special case for VFS use.
From the LWIP side:
- We set LWIP_SOCKET_OFFSET dynamically at VFS registration time so that native LWIP socket functions & VFS functions
see the same fd space. This is necessary to mix POSIX file operations defined in VFS with POSIX socket operations defined
in LWIP, without needing to wrap all of them.
From the VFS side:
- ESP_VFS_FLAG_SHARED_FD_SPACE is set, so unlike other VFS implementations the FDs that the LWIP "VFS" sees and the
FDs that the user sees are the same FDs.
/* Non-LWIP file descriptors are from 0 to (LWIP_SOCKET_OFFSET-1).
* LWIP file descriptors are from LWIP_SOCKET_OFFSET to FD_SETSIZE-1.
*/
int lwip_socket_offset;
_Static_assert(MAX_FDS >= CONFIG_LWIP_MAX_SOCKETS, "MAX_FDS < CONFIG_LWIP_MAX_SOCKETS");
static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args);
static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args);
@ -45,7 +37,7 @@ static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args);
void esp_vfs_lwip_sockets_register()
{
esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT | ESP_VFS_FLAG_SHARED_FD_SPACE,
.flags = ESP_VFS_FLAG_DEFAULT,
.write = &lwip_write_r,
.open = NULL,
.fstat = NULL,
@ -54,14 +46,8 @@ void esp_vfs_lwip_sockets_register()
.fcntl = &lwip_fcntl_r_wrapper,
.ioctl = &lwip_ioctl_r_wrapper,
};
int max_fd;
ESP_ERROR_CHECK(esp_vfs_register_socket_space(&vfs, NULL, &lwip_socket_offset, &max_fd));
/* LWIP can't be allowed to create more sockets than fit in the per-VFS fd space. Currently this isn't configurable
* but it's set much larger than CONFIG_LWIP_MAX_SOCKETS should ever be (max 2^12 FDs).
*/
assert(max_fd >= lwip_socket_offset && CONFIG_LWIP_MAX_SOCKETS <= max_fd - lwip_socket_offset);
ESP_ERROR_CHECK(esp_vfs_register_fd_range(&vfs, NULL, LWIP_SOCKET_OFFSET, MAX_FDS));
}
static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args)
@ -73,5 +59,3 @@ static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args)
{
return lwip_ioctl_r(fd, cmd, va_arg(args, void *));
}

View File

@ -221,9 +221,6 @@ typedef unsigned int mode_t _ST_INT32;
typedef unsigned short nlink_t;
/* FD_SET and friends are still LWIP only */
# if !defined(ESP_PLATFORM)
/* We don't define fd_set and friends if we are compiling POSIX
source, or if we have included (or may include as indicated
by __USE_W32_SOCKETS) the W32api winsock[2].h header which
@ -269,7 +266,6 @@ typedef struct _types_fd_set {
}))
# endif /* !(defined (_POSIX_SOURCE) || defined (_WINSOCK_H) || defined (_WINSOCKAPI_) || defined (__USE_W32_SOCKETS)) */
#endif /* !defined(ESP_PLATFORM) */
#undef __MS_types__
#undef _ST_INT32

View File

@ -93,41 +93,10 @@ When opening files, FS driver will only be given relative path to files. For exa
VFS doesn't impose a limit on total file path length, but it does limit FS path prefix to ``ESP_VFS_PATH_MAX`` characters. Individual FS drivers may have their own filename length limitations.
File descriptors
----------------
It is suggested that filesystem drivers should use small positive integers as file descriptors. VFS component assumes that ``CONFIG_MAX_FD_BITS`` bits (12 by default) are sufficient to represent a file descriptor.
While file descriptors returned by VFS component to newlib library are rarely seen by the application, the following details may be useful for debugging purposes. File descriptors returned by VFS component are composed of two parts: FS driver ID, and the actual file descriptor. Because newlib stores file descriptors as 16-bit integers, VFS component is also limited by 16 bits to store both parts.
Lower ``CONFIG_MAX_FD_BITS`` bits are used to store zero-based file descriptor. The per-filesystem FD obtained from the FS ``open`` call, and this result is stored in the lower bits of the FD. Higher bits are used to save the index of FS in the internal table of registered filesystems.
When VFS component receives a call from newlib which has a file descriptor, this file descriptor is translated back to the FS-specific file descriptor. First, higher bits of FD are used to identify the FS. Then only the lower ``CONFIG_MAX_FD_BITS`` bits of the fd are masked in, and resulting FD is passed to the FS driver.
.. highlight:: none
::
FD as seen by newlib FD as seen by FS driver
+-------+---------------+ +------------------------+
| FS id | Zero—based FD | +--------------------------> |
+---+---+------+--------+ | +------------------------+
| | |
| +--------------+
|
| +-------------+
| | Table of |
| | registered |
| | filesystems |
| +-------------+ +-------------+
+-------> entry +----> esp_vfs_t |
index +-------------+ | structure |
| | | |
| | | |
+-------------+ +-------------+
File descriptors are small positive integers from ``0`` to ``FD_SETSIZE - 1`` where ``FD_SETSIZE`` is defined in newlib's ``sys/types.h``. The largest file descriptors (configured by ``CONFIG_LWIP_MAX_SOCKETS``) are reserved for sockets. The VFS component contains a lookup-table called ``s_fd_table`` for mapping global file descriptors to VFS driver indexes registered in the ``s_vfs`` array.
Standard IO streams (stdin, stdout, stderr)
-------------------------------------------

View File

@ -28,6 +28,15 @@
extern "C" {
#endif
#ifndef _SYS_TYPES_FD_SET
#error "VFS should be used with FD_SETSIZE and FD_SET from sys/types.h"
#endif
/**
* Maximum number of (global) file descriptors.
*/
#define MAX_FDS FD_SETSIZE /* for compatibility with fd_set and select() */
/**
* Maximum length of path prefix (not including zero terminator)
*/
@ -43,21 +52,6 @@ extern "C" {
*/
#define ESP_VFS_FLAG_CONTEXT_PTR 1
/**
* Flag which indicates that the FD space of the VFS implementation should be made
* the same as the FD space in newlib. This means that the normal masking off
* of VFS-independent fd bits is ignored and the full user-facing fd is passed to
* the VFS implementation.
*
* Set the p_minimum_fd & p_maximum_fd pointers when registering the socket in
* order to know what range of FDs can be used with the registered VFS.
*
* This is mostly useful for LWIP which shares the socket FD space with
* socket-specific functions.
*
*/
#define ESP_VFS_FLAG_SHARED_FD_SPACE 2
/**
* @brief VFS definition structure
*
@ -81,7 +75,7 @@ extern "C" {
*/
typedef struct
{
int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT, plus optionally ESP_VFS_FLAG_SHARED_FD_SPACE */
int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */
union {
ssize_t (*write_p)(void* p, int fd, const void * data, size_t size);
ssize_t (*write)(int fd, const void * data, size_t size);
@ -193,19 +187,20 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct
/**
* Special case function for registering a VFS that uses a method other than
* open() to open new file descriptors.
* open() to open new file descriptors from the interval <min_fd; max_fd).
*
* This is a special-purpose function intended for registering LWIP sockets to VFS.
*
* @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
* @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
* @param ctx Pointer to context structure. Meaning is the same as for esp_vfs_register().
* @param p_min_fd If non-NULL, on success this variable is written with the minimum (global/user-facing) FD that this VFS will use. This is useful when ESP_VFS_FLAG_SHARED_FD_SPACE is set in vfs->flags.
* @param p_max_fd If non-NULL, on success this variable is written with one higher than the maximum (global/user-facing) FD that this VFS will use. This is useful when ESP_VFS_FLAG_SHARED_FD_SPACE is set in vfs->flags.
* @param min_fd The smallest file descriptor this VFS will use.
* @param max_fd Upper boundary for file descriptors this VFS will use (the biggest file descriptor plus one).
*
* @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
* registered.
* registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries
* are incorrect.
*/
esp_err_t esp_vfs_register_socket_space(const esp_vfs_t *vfs, void *ctx, int *p_min_fd, int *p_max_fd);
esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd);
/**
* Unregister a virtual filesystem for given path prefix
@ -233,10 +228,8 @@ int esp_vfs_unlink(struct _reent *r, const char *path);
int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
/**@}*/
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__ESP_VFS_H__

View File

@ -0,0 +1,261 @@
// 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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/fcntl.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_vfs.h"
#include "unity.h"
#include "esp_log.h"
#define VFS_PREF1 "/vfs1"
#define VFS_PREF2 "/vfs2"
#define FILE1 "/file1"
typedef struct {
const char *path;
int fd;
} collision_test_vfs_param_t;
static int collision_test_vfs_open(void* ctx, const char * path, int flags, int mode)
{
const collision_test_vfs_param_t *param = (collision_test_vfs_param_t *) ctx;
if (strcmp(param->path, path) == 0) {
return param->fd;
}
errno = ENOENT;
return -1;
}
static int collision_test_vfs_close(void* ctx, int fd)
{
const collision_test_vfs_param_t *param = (collision_test_vfs_param_t *) ctx;
if (fd == param->fd) {
return 0;
}
errno = EBADF;
return -1;
}
TEST_CASE("FDs from different VFSs don't collide", "[vfs]")
{
collision_test_vfs_param_t param = {
.path = FILE1,
.fd = 1,
};
esp_vfs_t desc = {
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.open_p = collision_test_vfs_open,
.close_p = collision_test_vfs_close,
};
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, &param) );
TEST_ESP_OK( esp_vfs_register(VFS_PREF2, &desc, &param) );
const int fd1 = open(VFS_PREF1 FILE1, 0, 0);
const int fd2 = open(VFS_PREF2 FILE1, 0, 0);
TEST_ASSERT_NOT_EQUAL(fd1, -1);
TEST_ASSERT_NOT_EQUAL(fd2, -1);
// Both VFS drivers return local FD 1 but the global FDs returned by
// open() should not be the same
TEST_ASSERT_NOT_EQUAL(fd1, fd2);
TEST_ASSERT_NOT_EQUAL(close(fd1), -1);
TEST_ASSERT_NOT_EQUAL(close(fd2), -1);
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF2) );
}
#define FILE2 "/file2"
#define FILE3 "/file3"
#define FILE4 "/file4"
#define CONCURRENT_TEST_STACK_SIZE (2*1024)
#define CONCURRENT_TEST_MAX_WAIT (1000 / portTICK_PERIOD_MS)
typedef struct {
const char *path;
SemaphoreHandle_t done;
} concurrent_test_task_param_t;
typedef struct {
const char *path;
int local_fd;
} concurrent_test_path_to_fd_t;
static concurrent_test_path_to_fd_t concurrent_test_path_to_fd[] = {
{ .path = FILE1, .local_fd = 1 },
{ .path = FILE2, .local_fd = 2 },
{ .path = FILE3, .local_fd = 3 },
{ .path = FILE4, .local_fd = 4 },
};
static int concurrent_test_vfs_open(const char * path, int flags, int mode)
{
for (int i = 0; i < sizeof(concurrent_test_path_to_fd)/sizeof(concurrent_test_path_to_fd[0]); ++i) {
if (strcmp(concurrent_test_path_to_fd[i].path, path) == 0) {
// This behaves like UART: opening the same file gives always the
// same local FD (even when opening at the same time multiple FDs)
return concurrent_test_path_to_fd[i].local_fd;
}
}
errno = ENOENT;
return -1;
}
static int concurrent_test_vfs_close(int fd)
{
for (int i = 0; i < sizeof(concurrent_test_path_to_fd)/sizeof(concurrent_test_path_to_fd[0]); ++i) {
if (concurrent_test_path_to_fd[i].local_fd == fd) {
return 0;
}
}
errno = EBADF;
return -1;
}
static inline void test_delay_rand_ms(int ms)
{
vTaskDelay((rand() % ms) / portTICK_PERIOD_MS);
}
static void concurrent_task(void *param)
{
concurrent_test_task_param_t *task_param = (concurrent_test_task_param_t *) param;
test_delay_rand_ms(10);
for (int i = 0; i < 10; ++i) {
const int global_fd = open(task_param->path, 0, 0);
TEST_ASSERT_NOT_EQUAL(global_fd, -1);
test_delay_rand_ms(10);
TEST_ASSERT_NOT_EQUAL(close(global_fd), -1);
test_delay_rand_ms(10);
}
xSemaphoreGive(task_param->done);
vTaskDelete(NULL);
}
TEST_CASE("VFS can handle concurrent open/close requests", "[vfs]")
{
esp_vfs_t desc = {
.flags = ESP_VFS_FLAG_DEFAULT,
.open = concurrent_test_vfs_open,
.close = concurrent_test_vfs_close,
};
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, NULL) );
concurrent_test_task_param_t param1 = { .path = VFS_PREF1 FILE1, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param2 = { .path = VFS_PREF1 FILE1, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param3 = { .path = VFS_PREF1 FILE2, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param4 = { .path = VFS_PREF1 FILE2, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param5 = { .path = VFS_PREF1 FILE3, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param6 = { .path = VFS_PREF1 FILE3, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param7 = { .path = VFS_PREF1 FILE4, .done = xSemaphoreCreateBinary() };
concurrent_test_task_param_t param8 = { .path = VFS_PREF1 FILE4, .done = xSemaphoreCreateBinary() };
TEST_ASSERT_NOT_NULL(param1.done);
TEST_ASSERT_NOT_NULL(param2.done);
TEST_ASSERT_NOT_NULL(param3.done);
TEST_ASSERT_NOT_NULL(param4.done);
TEST_ASSERT_NOT_NULL(param5.done);
TEST_ASSERT_NOT_NULL(param6.done);
TEST_ASSERT_NOT_NULL(param7.done);
TEST_ASSERT_NOT_NULL(param8.done);
const int cpuid0 = 0;
const int cpuid1 = portNUM_PROCESSORS - 1;
srand(time(NULL));
xTaskCreatePinnedToCore(concurrent_task, "t1", CONCURRENT_TEST_STACK_SIZE, &param1, 3, NULL, cpuid0);
xTaskCreatePinnedToCore(concurrent_task, "t2", CONCURRENT_TEST_STACK_SIZE, &param2, 3, NULL, cpuid1);
xTaskCreatePinnedToCore(concurrent_task, "t3", CONCURRENT_TEST_STACK_SIZE, &param3, 3, NULL, cpuid0);
xTaskCreatePinnedToCore(concurrent_task, "t4", CONCURRENT_TEST_STACK_SIZE, &param4, 3, NULL, cpuid1);
xTaskCreatePinnedToCore(concurrent_task, "t5", CONCURRENT_TEST_STACK_SIZE, &param5, 3, NULL, cpuid0);
xTaskCreatePinnedToCore(concurrent_task, "t6", CONCURRENT_TEST_STACK_SIZE, &param6, 3, NULL, cpuid1);
xTaskCreatePinnedToCore(concurrent_task, "t7", CONCURRENT_TEST_STACK_SIZE, &param7, 3, NULL, cpuid0);
xTaskCreatePinnedToCore(concurrent_task, "t8", CONCURRENT_TEST_STACK_SIZE, &param8, 3, NULL, cpuid1);
TEST_ASSERT_EQUAL(xSemaphoreTake(param1.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param2.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param3.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param4.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param5.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param6.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param7.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
TEST_ASSERT_EQUAL(xSemaphoreTake(param8.done, CONCURRENT_TEST_MAX_WAIT), pdTRUE);
vSemaphoreDelete(param1.done);
vSemaphoreDelete(param2.done);
vSemaphoreDelete(param3.done);
vSemaphoreDelete(param4.done);
vSemaphoreDelete(param5.done);
vSemaphoreDelete(param6.done);
vSemaphoreDelete(param7.done);
vSemaphoreDelete(param8.done);
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
}
static int time_test_vfs_open(const char *path, int flags, int mode)
{
return 1;
}
static int time_test_vfs_close(int fd)
{
return 1;
}
int time_test_vfs_write(int fd, const void *data, size_t size)
{
return size;
}
TEST_CASE("Open & write & close through VFS passes performance test", "[vfs]")
{
esp_vfs_t desc = {
.flags = ESP_VFS_FLAG_DEFAULT,
.open = time_test_vfs_open,
.close = time_test_vfs_close,
.write = time_test_vfs_write,
};
TEST_ESP_OK( esp_vfs_register(VFS_PREF1, &desc, NULL) );
const int64_t begin = esp_timer_get_time();
const int iter_count = 1000;
for (int i = 0; i < iter_count; ++i) {
const int fd = open(VFS_PREF1 FILE1, 0, 0);
TEST_ASSERT_NOT_EQUAL(fd, -1);
write(fd, "a", 1);
TEST_ASSERT_NOT_EQUAL(close(fd), -1);
}
const int64_t time_diff_us = esp_timer_get_time() - begin;
const int ns_per_iter = (int) (time_diff_us * 1000 / iter_count);
TEST_ESP_OK( esp_vfs_unregister(VFS_PREF1) );
TEST_PERFORMANCE_LESS_THAN(VFS_OPEN_WRITE_CLOSE_TIME, "%dns", ns_per_iter);
}

View File

@ -19,33 +19,27 @@
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/unistd.h>
#include <sys/lock.h>
#include <dirent.h>
#include "esp_vfs.h"
#include "esp_log.h"
/*
* File descriptors visible by the applications are composed of two parts.
* Lower CONFIG_MAX_FD_BITS bits are used for the actual file descriptor.
* Next (16 - CONFIG_MAX_FD_BITS - 1) bits are used to identify the VFS this
* descriptor corresponds to.
* Highest bit is zero.
* We can only use 16 bits because newlib stores file descriptor as short int.
*/
#ifndef CONFIG_MAX_FD_BITS
#define CONFIG_MAX_FD_BITS 12
#endif
#define MAX_VFS_ID_BITS (16 - CONFIG_MAX_FD_BITS - 1)
// mask of actual file descriptor (e.g. 0x00000fff)
#define VFS_FD_MASK ((1 << CONFIG_MAX_FD_BITS) - 1)
// max number of VFS entries
#define VFS_MAX_COUNT ((1 << MAX_VFS_ID_BITS) - 1)
// mask of VFS id (e.g. 0x00007000)
#define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS)
#define VFS_INDEX_S CONFIG_MAX_FD_BITS
#define VFS_MAX_COUNT 8 /* max number of VFS entries (registered filesystems) */
#define LEN_PATH_PREFIX_IGNORED SIZE_MAX /* special length value for VFS which is never recognised by open() */
#define FD_TABLE_ENTRY_UNUSED (fd_table_t) { .permanent = false, .vfs_index = -1, .local_fd = -1 }
typedef uint8_t local_fd_t;
_Static_assert((1 << (sizeof(local_fd_t)*8)) >= MAX_FDS, "file descriptor type too small");
typedef int8_t vfs_index_t;
_Static_assert((1 << (sizeof(vfs_index_t)*8)) >= VFS_MAX_COUNT, "VFS index type too small");
_Static_assert(((vfs_index_t) -1) < 0, "vfs_index_t must be a signed type");
typedef struct {
bool permanent;
vfs_index_t vfs_index;
local_fd_t local_fd;
} fd_table_t;
typedef struct vfs_entry_ {
esp_vfs_t vfs; // contains pointers to VFS functions
@ -58,7 +52,10 @@ typedef struct vfs_entry_ {
static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 };
static size_t s_vfs_count = 0;
static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *p_minimum_fd, int *p_maximum_fd)
static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED };
static _lock_t s_fd_table_lock;
static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index)
{
if (len != LEN_PATH_PREFIX_IGNORED) {
if ((len != 0 && len < 2) || (len > ESP_VFS_PATH_MAX)) {
@ -96,11 +93,8 @@ static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, cons
entry->ctx = ctx;
entry->offset = index;
if (p_minimum_fd != NULL) {
*p_minimum_fd = index << VFS_INDEX_S;
}
if (p_maximum_fd != NULL) {
*p_maximum_fd = (index + 1) << VFS_INDEX_S;
if (vfs_index) {
*vfs_index = index;
}
return ESP_OK;
@ -108,12 +102,40 @@ static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, cons
esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx)
{
return esp_vfs_register_common(base_path, strlen(base_path), vfs, ctx, NULL, NULL);
return esp_vfs_register_common(base_path, strlen(base_path), vfs, ctx, NULL);
}
esp_err_t esp_vfs_register_socket_space(const esp_vfs_t *vfs, void *ctx, int *p_min_fd, int *p_max_fd)
esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd)
{
return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, p_min_fd, p_max_fd);
if (min_fd < 0 || max_fd < 0 || min_fd > MAX_FDS || max_fd > MAX_FDS || min_fd > max_fd) {
return ESP_ERR_INVALID_ARG;
}
int index = -1;
esp_err_t ret = esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, &index);
if (ret == ESP_OK) {
_lock_acquire(&s_fd_table_lock);
for (int i = min_fd; i < max_fd; ++i) {
if (s_fd_table[i].vfs_index != -1) {
free(s_vfs[i]);
s_vfs[i] = NULL;
for (int j = min_fd; j < i; ++j) {
if (s_fd_table[j].vfs_index == index) {
s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
}
}
_lock_release(&s_fd_table_lock);
return ESP_ERR_INVALID_ARG;
}
s_fd_table[i].permanent = true;
s_fd_table[i].vfs_index = index;
s_fd_table[i].local_fd = i;
}
_lock_release(&s_fd_table_lock);
}
return ret;
}
esp_err_t esp_vfs_unregister(const char* base_path)
@ -126,28 +148,55 @@ esp_err_t esp_vfs_unregister(const char* base_path)
if (memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) {
free(vfs);
s_vfs[i] = NULL;
_lock_acquire(&s_fd_table_lock);
// Delete all references from the FD lookup-table
for (int j = 0; j < MAX_FDS; ++j) {
if (s_fd_table[j].vfs_index == i) {
s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
}
}
_lock_release(&s_fd_table_lock);
return ESP_OK;
}
}
return ESP_ERR_INVALID_STATE;
}
static const vfs_entry_t* get_vfs_for_fd(int fd)
static inline const vfs_entry_t *get_vfs_for_index(int index)
{
int index = ((fd & VFS_INDEX_MASK) >> VFS_INDEX_S);
if (index >= s_vfs_count) {
if (index < 0 || index >= s_vfs_count) {
return NULL;
} else {
return s_vfs[index];
}
return s_vfs[index];
}
static int translate_fd(const vfs_entry_t* vfs, int fd)
static inline bool fd_valid(int fd)
{
if (vfs->vfs.flags & ESP_VFS_FLAG_SHARED_FD_SPACE) {
return fd;
} else {
return fd & VFS_FD_MASK;
return (fd < MAX_FDS) && (fd >= 0);
}
static const vfs_entry_t *get_vfs_for_fd(int fd)
{
const vfs_entry_t *vfs = NULL;
if (fd_valid(fd)) {
const int index = s_fd_table[fd].vfs_index; // single read -> no locking is required
vfs = get_vfs_for_index(index);
}
return vfs;
}
static inline int get_local_fd(const vfs_entry_t *vfs, int fd)
{
int local_fd = -1;
if (vfs && fd_valid(fd)) {
local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
}
return local_fd;
}
static const char* translate_path(const vfs_entry_t* vfs, const char* src_path)
@ -248,28 +297,44 @@ static const vfs_entry_t* get_vfs_for_path(const char* path)
int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode)
{
const vfs_entry_t* vfs = get_vfs_for_path(path);
const vfs_entry_t *vfs = get_vfs_for_path(path);
if (vfs == NULL) {
__errno_r(r) = ENOENT;
return -1;
}
const char* path_within_vfs = translate_path(vfs, path);
int ret;
CHECK_AND_CALL(ret, r, vfs, open, path_within_vfs, flags, mode);
if (ret < 0) {
return ret;
const char *path_within_vfs = translate_path(vfs, path);
int fd_within_vfs;
CHECK_AND_CALL(fd_within_vfs, r, vfs, open, path_within_vfs, flags, mode);
if (fd_within_vfs >= 0) {
_lock_acquire(&s_fd_table_lock);
for (int i = 0; i < MAX_FDS; ++i) {
if (s_fd_table[i].vfs_index == -1) {
s_fd_table[i].permanent = false;
s_fd_table[i].vfs_index = vfs->offset;
s_fd_table[i].local_fd = fd_within_vfs;
_lock_release(&s_fd_table_lock);
return i;
}
}
_lock_release(&s_fd_table_lock);
int ret;
CHECK_AND_CALL(ret, r, vfs, close, fd_within_vfs);
(void) ret; // remove "set but not used" warning
__errno_r(r) = ENOMEM;
return -1;
}
return ret + (vfs->offset << VFS_INDEX_S);
__errno_r(r) = ENOENT;
return -1;
}
ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
const int local_fd = get_local_fd(vfs, fd);
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
ssize_t ret;
CHECK_AND_CALL(ret, r, vfs, write, local_fd, data, size);
return ret;
@ -278,11 +343,11 @@ ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size)
off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
const int local_fd = get_local_fd(vfs, fd);
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
off_t ret;
CHECK_AND_CALL(ret, r, vfs, lseek, local_fd, size, mode);
return ret;
@ -291,11 +356,11 @@ off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode)
ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
const int local_fd = get_local_fd(vfs, fd);
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
ssize_t ret;
CHECK_AND_CALL(ret, r, vfs, read, local_fd, dst, size);
return ret;
@ -305,24 +370,30 @@ ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size)
int esp_vfs_close(struct _reent *r, int fd)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
const int local_fd = get_local_fd(vfs, fd);
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
CHECK_AND_CALL(ret, r, vfs, close, local_fd);
_lock_acquire(&s_fd_table_lock);
if (!s_fd_table[fd].permanent) {
s_fd_table[fd] = FD_TABLE_ENTRY_UNUSED;
}
_lock_release(&s_fd_table_lock);
return ret;
}
int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
const int local_fd = get_local_fd(vfs, fd);
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
CHECK_AND_CALL(ret, r, vfs, fstat, local_fd, st);
return ret;
@ -404,14 +475,14 @@ DIR* opendir(const char* name)
DIR* ret;
CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs);
if (ret != NULL) {
ret->dd_vfs_idx = vfs->offset << VFS_INDEX_S;
ret->dd_vfs_idx = vfs->offset;
}
return ret;
}
struct dirent* readdir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
__errno_r(r) = EBADF;
@ -424,7 +495,7 @@ struct dirent* readdir(DIR* pdir)
int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
@ -437,7 +508,7 @@ int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent)
long telldir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
@ -450,7 +521,7 @@ long telldir(DIR* pdir)
void seekdir(DIR* pdir, long loc)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
@ -466,7 +537,7 @@ void rewinddir(DIR* pdir)
int closedir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
const vfs_entry_t* vfs = get_vfs_for_index(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
@ -508,12 +579,12 @@ int rmdir(const char* name)
int fcntl(int fd, int cmd, ...)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
const int local_fd = get_local_fd(vfs, fd);
struct _reent* r = __getreent();
if (vfs == NULL) {
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
va_list args;
va_start(args, cmd);
@ -525,12 +596,12 @@ int fcntl(int fd, int cmd, ...)
int ioctl(int fd, int cmd, ...)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
const int local_fd = get_local_fd(vfs, fd);
struct _reent* r = __getreent();
if (vfs == NULL) {
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
va_list args;
va_start(args, cmd);
@ -542,12 +613,12 @@ int ioctl(int fd, int cmd, ...)
int fsync(int fd)
{
const vfs_entry_t* vfs = get_vfs_for_fd(fd);
const int local_fd = get_local_fd(vfs, fd);
struct _reent* r = __getreent();
if (vfs == NULL) {
if (vfs == NULL || local_fd < 0) {
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
CHECK_AND_CALL(ret, r, vfs, fsync, local_fd);
return ret;

View File

@ -291,22 +291,22 @@ void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode)
s_tx_mode = mode;
}
void esp_vfs_dev_uart_use_nonblocking(int fd)
void esp_vfs_dev_uart_use_nonblocking(int uart_num)
{
_lock_acquire_recursive(&s_uart_read_locks[fd]);
_lock_acquire_recursive(&s_uart_write_locks[fd]);
s_uart_tx_func[fd] = uart_tx_char;
s_uart_rx_func[fd] = uart_rx_char;
_lock_release_recursive(&s_uart_write_locks[fd]);
_lock_release_recursive(&s_uart_read_locks[fd]);
_lock_acquire_recursive(&s_uart_read_locks[uart_num]);
_lock_acquire_recursive(&s_uart_write_locks[uart_num]);
s_uart_tx_func[uart_num] = uart_tx_char;
s_uart_rx_func[uart_num] = uart_rx_char;
_lock_release_recursive(&s_uart_write_locks[uart_num]);
_lock_release_recursive(&s_uart_read_locks[uart_num]);
}
void esp_vfs_dev_uart_use_driver(int fd)
void esp_vfs_dev_uart_use_driver(int uart_num)
{
_lock_acquire_recursive(&s_uart_read_locks[fd]);
_lock_acquire_recursive(&s_uart_write_locks[fd]);
s_uart_tx_func[fd] = uart_tx_char_via_driver;
s_uart_rx_func[fd] = uart_rx_char_via_driver;
_lock_release_recursive(&s_uart_write_locks[fd]);
_lock_release_recursive(&s_uart_read_locks[fd]);
_lock_acquire_recursive(&s_uart_read_locks[uart_num]);
_lock_acquire_recursive(&s_uart_write_locks[uart_num]);
s_uart_tx_func[uart_num] = uart_tx_char_via_driver;
s_uart_rx_func[uart_num] = uart_rx_char_via_driver;
_lock_release_recursive(&s_uart_write_locks[uart_num]);
_lock_release_recursive(&s_uart_read_locks[uart_num]);
}