From a320fed3b5fd55a7e914ac9a6630d22b6d2ebc06 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 09:24:40 +1100 Subject: [PATCH 1/7] vfs: Add ioctl() to filesystem set --- components/vfs/include/esp_vfs.h | 4 ++++ components/vfs/vfs.c | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 6ced2ce2b8..fcd2e66bbb 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -145,6 +145,10 @@ typedef struct int (*fcntl_p)(void* ctx, int fd, int cmd, va_list args); int (*fcntl)(int fd, int cmd, va_list args); }; + union { + int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args); + int (*ioctl)(int fd, int cmd, va_list args); + }; } esp_vfs_t; diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index f07bf7c37a..c307fdef11 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -489,3 +489,20 @@ int fcntl(int fd, int cmd, ...) va_end(args); return ret; } + +int ioctl(int fd, int cmd, ...) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + va_list args; + va_start(args, cmd); + CHECK_AND_CALL(ret, r, vfs, ioctl, local_fd, cmd, args); + va_end(args); + return ret; +} From 3ebf7923d3d4370bc5e8a823fd1ae209bb2b8bc3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Oct 2017 16:56:55 +1100 Subject: [PATCH 2/7] lwip: Route LWIP socket POSIX I/O functions via IDF VFS layer No more conflicts between LWIP & newlib read(), write(), fcntl(), etc. select() still only works if all of the fds are sockets. Closes https://github.com/espressif/esp-idf/issues/273 --- .../lwip/include/lwip/port/arch/sys_arch.h | 1 + .../lwip/include/lwip/port/arch/vfs_lwip.h | 28 +++++++ components/lwip/include/lwip/port/lwipopts.h | 14 ++++ components/lwip/port/freertos/sys_arch.c | 1 + components/lwip/port/vfs_lwip.c | 78 +++++++++++++++++++ components/vfs/include/esp_vfs.h | 33 +++++++- components/vfs/vfs.c | 49 +++++++++--- 7 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 components/lwip/include/lwip/port/arch/vfs_lwip.h create mode 100644 components/lwip/port/vfs_lwip.c diff --git a/components/lwip/include/lwip/port/arch/sys_arch.h b/components/lwip/include/lwip/port/arch/sys_arch.h index 716fd9fa53..bb7ea18af7 100644 --- a/components/lwip/include/lwip/port/arch/sys_arch.h +++ b/components/lwip/include/lwip/port/arch/sys_arch.h @@ -37,6 +37,7 @@ #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" +#include "arch/vfs_lwip.h" #ifdef __cplusplus extern "C" { diff --git a/components/lwip/include/lwip/port/arch/vfs_lwip.h b/components/lwip/include/lwip/port/arch/vfs_lwip.h new file mode 100644 index 0000000000..59ed087c51 --- /dev/null +++ b/components/lwip/include/lwip/port/arch/vfs_lwip.h @@ -0,0 +1,28 @@ +// Copyright 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. + +#ifdef __cplusplus +extern "C" { +#endif + +/* Internal declarations used to ingreate LWIP port layer + to ESP-IDF VFS for POSIX I/O. +*/ +extern unsigned lwip_socket_offset; + +void esp_vfs_lwip_sockets_register(); + +#ifdef __cplusplus +} +#endif diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 47b80ff616..907f7db2e8 100644 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -34,6 +34,7 @@ #include #include +#include #include #include #include "esp_task.h" @@ -687,6 +688,19 @@ #define ETHARP_TRUST_IP_MAC CONFIG_LWIP_ETHARP_TRUST_IP_MAC +/** + * POSIX I/O functions are mapped to LWIP via the VFS layer + * (see port/vfs_lwip.c) + */ +#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) + */ +#define LWIP_SOCKET_OFFSET lwip_socket_offset + /* Enable all Espressif-only options */ #define ESP_LWIP 1 diff --git a/components/lwip/port/freertos/sys_arch.c b/components/lwip/port/freertos/sys_arch.c index 5f1ca08989..6f14a3a299 100755 --- a/components/lwip/port/freertos/sys_arch.c +++ b/components/lwip/port/freertos/sys_arch.c @@ -433,6 +433,7 @@ sys_init(void) if (ERR_OK != sys_mutex_new(&g_lwip_protect_mutex)) { ESP_LOGE(TAG, "sys_init: failed to init lwip protect mutex\n"); } + esp_vfs_lwip_sockets_register(); } /*-----------------------------------------------------------------------------------*/ diff --git a/components/lwip/port/vfs_lwip.c b/components/lwip/port/vfs_lwip.c new file mode 100644 index 0000000000..b1f064c331 --- /dev/null +++ b/components/lwip/port/vfs_lwip.c @@ -0,0 +1,78 @@ +// Copyright 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 "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "esp_attr.h" +#include "soc/uart_struct.h" +#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. +*/ + +unsigned lwip_socket_offset; + +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); + +void esp_vfs_lwip_sockets_register() +{ + esp_vfs_t vfs = { + .fd_offset = 0, + .flags = ESP_VFS_FLAG_DEFAULT | ESP_VFS_FLAG_SHARED_FD_SPACE, + .write = &lwip_write_r, + .open = NULL, + .fstat = NULL, + .close = &lwip_close_r, + .read = &lwip_read_r, + .fcntl = &lwip_fcntl_r_wrapper, + .ioctl = &lwip_ioctl_r_wrapper, + }; + unsigned 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(CONFIG_LWIP_MAX_SOCKETS <= max_fd - lwip_socket_offset); +} + +static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args) +{ + return lwip_fcntl_r(fd, cmd, va_arg(args, int)); +} + +static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args) +{ + return lwip_ioctl_r(fd, cmd, va_arg(args, void *)); +} + + diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index fcd2e66bbb..6a8ed3306d 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -43,6 +43,21 @@ 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 * @@ -68,7 +83,7 @@ extern "C" { typedef struct { int fd_offset; /*!< file descriptor offset, determined by the FS driver */ - int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */ + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT, plus optionally ESP_VFS_FLAG_SHARED_FD_SPACE */ 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); @@ -174,6 +189,22 @@ typedef struct esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); +/** + * Special case function for registering a VFS that uses a method other than + * open() to open new file descriptors. + * + * 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 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. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are + * registered. + */ +esp_err_t esp_vfs_register_socket_space(const esp_vfs_t *vfs, void *ctx, unsigned *p_min_fd, unsigned *p_max_fd); + /** * Unregister a virtual filesystem for given path prefix * diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index c307fdef11..f3624259b6 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -41,6 +41,8 @@ #define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS) #define VFS_INDEX_S CONFIG_MAX_FD_BITS +#define LEN_PATH_PREFIX_IGNORED SIZE_MAX /* special length value for VFS which is never recognised by open() */ + typedef struct vfs_entry_ { esp_vfs_t vfs; // contains pointers to VFS functions char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS @@ -52,14 +54,15 @@ typedef struct vfs_entry_ { static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; static size_t s_vfs_count = 0; -esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) +static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, unsigned *p_minimum_fd, unsigned *p_maximum_fd) { - size_t len = strlen(base_path); - if ((len != 0 && len < 2)|| len > ESP_VFS_PATH_MAX) { - return ESP_ERR_INVALID_ARG; - } - if ((len > 0 && base_path[0] != '/') || base_path[len - 1] == '/') { - return ESP_ERR_INVALID_ARG; + if (len != LEN_PATH_PREFIX_IGNORED) { + if ((len != 0 && len < 2) || (len > ESP_VFS_PATH_MAX)) { + return ESP_ERR_INVALID_ARG; + } + if ((len > 0 && base_path[0] != '/') || base_path[len - 1] == '/') { + return ESP_ERR_INVALID_ARG; + } } vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); if (entry == NULL) { @@ -79,14 +82,36 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct ++s_vfs_count; } s_vfs[index] = entry; - strcpy(entry->path_prefix, base_path); // we have already verified argument length + if (len != LEN_PATH_PREFIX_IGNORED) { + strcpy(entry->path_prefix, base_path); // we have already verified argument length + } else { + bzero(entry->path_prefix, sizeof(entry->path_prefix)); + } memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); entry->path_prefix_len = len; 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; + } + return ESP_OK; } +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); +} + +esp_err_t esp_vfs_register_socket_space(const esp_vfs_t *vfs, void *ctx, unsigned *p_min_fd, unsigned *p_max_fd) +{ + return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, p_min_fd, p_max_fd); +} + esp_err_t esp_vfs_unregister(const char* base_path) { for (size_t i = 0; i < s_vfs_count; ++i) { @@ -114,7 +139,11 @@ static const vfs_entry_t* get_vfs_for_fd(int fd) static int translate_fd(const vfs_entry_t* vfs, int fd) { - return (fd & VFS_FD_MASK) + vfs->vfs.fd_offset; + if (vfs->vfs.flags & ESP_VFS_FLAG_SHARED_FD_SPACE) { + return fd + vfs->vfs.fd_offset; + } else { + return (fd & VFS_FD_MASK) + vfs->vfs.fd_offset; + } } static const char* translate_path(const vfs_entry_t* vfs, const char* src_path) @@ -134,7 +163,7 @@ static const vfs_entry_t* get_vfs_for_path(const char* path) size_t len = strlen(path); for (size_t i = 0; i < s_vfs_count; ++i) { const vfs_entry_t* vfs = s_vfs[i]; - if (!vfs) { + if (!vfs || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { continue; } // match path prefix From 48d9d507aaf50bbac023ac9fa888cb28c6bdf2c8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 09:53:06 +1100 Subject: [PATCH 3/7] newlib: Remove fcntl() implementation from libc Allows fcntl() implementation in vfs to be used. Closes https://github.com/espressif/esp-idf/issues/1070 Note this is different to the other libc file-related syscalls, as there is no reent structure involved. --- components/newlib/lib/libc-psram-workaround.a | Bin 5930838 -> 5925086 bytes components/newlib/lib/libc.a | Bin 5022558 -> 5016906 bytes components/newlib/lib/libc_nano.a | Bin 3569308 -> 3563656 bytes components/newlib/libc_discard.list | 2 ++ 4 files changed, 2 insertions(+) diff --git a/components/newlib/lib/libc-psram-workaround.a b/components/newlib/lib/libc-psram-workaround.a index cddebee59ec65d9eefd798f6af5b67ede0b476b8..27557575ae29a8a1908365d368c67e192e8dbecf 100644 GIT binary patch delta 4468 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 K?fkw`=cNJQt1h&a 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

tPHeT7|oTK}F&YgNo)G1{KQ+2;ObLpmH{W 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 diff --git a/components/newlib/libc_discard.list b/components/newlib/libc_discard.list index 8acce71569..c52b1bc5b9 100644 --- a/components/newlib/libc_discard.list +++ b/components/newlib/libc_discard.list @@ -77,6 +77,7 @@ lib_a-close.o lib_a-creat.o lib_a-environ.o lib_a-fclose.o +lib_a-fcntlr.o lib_a-isalnum.o lib_a-isalpha.o lib_a-isascii.o @@ -131,6 +132,7 @@ lib_a-strupr.o lib_a-stdio.o lib_a-syssbrk.o lib_a-sysclose.o +lib_a-sysfcntl.o lib_a-sysopen.o creat.o lib_a-sysread.o From 3f83914f7aa2916cbff9b03f5df8533bfed9f9f5 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 10:18:10 +1100 Subject: [PATCH 4/7] lwip: Fix bug with LWIP_SOCKET_OFFSET & IGMP group support --- components/lwip/api/sockets.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 0da123839c..b09cff19fc 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -3139,13 +3139,14 @@ static void lwip_socket_drop_registered_memberships(int s) (default initialization is to 0) */ int sa = s + 1; int i; + struct lwip_sock *sock = get_socket(s); - LWIP_ASSERT("socket has no netconn", sockets[s].conn != NULL); + LWIP_ASSERT("socket has no netconn", sock->conn != NULL); for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) { if (socket_multicast_memberships[i].sa == sa) { socket_multicast_memberships[i].sa = 0; - netconn_join_leave_group(sockets[s].conn, + netconn_join_leave_group(sock->conn, &socket_multicast_memberships[i].multi_addr, &socket_multicast_memberships[i].if_addr, NETCONN_LEAVE); From 541493d87769e0fa7fa128f7bdd0c2b5bcaa25d0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 10:41:09 +1100 Subject: [PATCH 5/7] lwip: Check for underflow in FD_SET()/FD_GET() select() only works with LWIP sockets which have a high LWIP_SOCKET_OFFSET, so chance of accidental underflow is high. --- components/lwip/include/lwip/lwip/sockets.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index cb458988a6..10e8ace038 100755 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -431,9 +431,9 @@ typedef struct ip_mreq { /* Make FD_SETSIZE match NUM_SOCKETS in socket.c */ #define FD_SETSIZE MEMP_NUM_NETCONN #define FDSETSAFESET(n, code) do { \ - if (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0)) { \ + if (n >= LWIP_SOCKET_OFFSET && ((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0)) { \ code; }} while(0) -#define FDSETSAFEGET(n, code) (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0) ?\ +#define FDSETSAFEGET(n, code) (n >= LWIP_SOCKET_OFFSET && ((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0) ?\ (code) : 0) #define FD_SET(n, p) FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] |= (1 << (((n)-LWIP_SOCKET_OFFSET) & 7))) #define FD_CLR(n, p) FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] &= ~(1 << (((n)-LWIP_SOCKET_OFFSET) & 7))) From 4a9d4587b735068cc9b5806a043494c78f4bfb9b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 10:55:54 +1100 Subject: [PATCH 6/7] vfs: Add C++ guards to esp_vfs_dev.h Closes https://github.com/espressif/esp-idf/issues/1069 --- components/vfs/include/esp_vfs_dev.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/vfs/include/esp_vfs_dev.h b/components/vfs/include/esp_vfs_dev.h index b51527fcfc..b330b4c565 100644 --- a/components/vfs/include/esp_vfs_dev.h +++ b/components/vfs/include/esp_vfs_dev.h @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef __ESP_VFS_DEV_H__ -#define __ESP_VFS_DEV_H__ +#pragma once #include "esp_vfs.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief Line ending settings */ @@ -81,4 +84,6 @@ void esp_vfs_dev_uart_use_nonblocking(int uart_num); */ void esp_vfs_dev_uart_use_driver(int uart_num); -#endif //__ESP_VFS_DEV_H__ +#ifdef __cplusplus +} +#endif From 539262b5c2f186154092d727863dc39cb0c2ceee Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Oct 2017 16:11:12 +1100 Subject: [PATCH 7/7] vfs: Remove fd_offset member This was intended for integrating LWIP, but a different approach was used. --- components/lwip/port/vfs_lwip.c | 1 - components/vfs/README.rst | 49 ++++++++++++++------------------ components/vfs/include/esp_vfs.h | 8 ++---- components/vfs/vfs.c | 7 ++--- components/vfs/vfs_uart.c | 1 - 5 files changed, 28 insertions(+), 38 deletions(-) diff --git a/components/lwip/port/vfs_lwip.c b/components/lwip/port/vfs_lwip.c index b1f064c331..b3c6261924 100644 --- a/components/lwip/port/vfs_lwip.c +++ b/components/lwip/port/vfs_lwip.c @@ -45,7 +45,6 @@ static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args); void esp_vfs_lwip_sockets_register() { esp_vfs_t vfs = { - .fd_offset = 0, .flags = ESP_VFS_FLAG_DEFAULT | ESP_VFS_FLAG_SHARED_FD_SPACE, .write = &lwip_write_r, .open = NULL, diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 9437055393..2b2a6bc9fb 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -22,7 +22,6 @@ To register an FS driver, application needs to define in instance of esp_vfs_t s :: esp_vfs_t myfs = { - .fd_offset = 0, .flags = ESP_VFS_FLAG_DEFAULT, .write = &myfs_write, .open = &myfs_open, @@ -43,7 +42,7 @@ Case 1: API functions are declared without an extra context pointer (FS driver i .flags = ESP_VFS_FLAG_DEFAULT, .write = &myfs_write, // ... other members initialized - + // When registering FS, context pointer (third argument) is NULL: ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); @@ -55,7 +54,7 @@ Case 2: API functions are declared with an extra context pointer (FS driver supp .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 // (hypothetical myfs_mount function is used for illustrative purposes) myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size); @@ -100,37 +99,34 @@ 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. -If filesystem is configured with an option to offset all file descriptors by a constant value, such value should be passed to ``fd_offset`` field of ``esp_vfs_t`` structure. VFS component will then remove this offset when working with FDs of that specific FS, bringing them into the range of small positive integers. +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. -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. -Lower ``CONFIG_MAX_FD_BITS`` bits are used to store zero-based file descriptor. If FS driver has a non-zero ``fd_offset`` field, this ``fd_offset`` is subtracted FDs obtained from the FS ``open`` call, and the 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 ``fd_offset`` field of the FS is added to the lower ``CONFIG_MAX_FD_BITS`` bits of the fd, and resulting FD is passed to the FS driver. +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 | +---------------> sum +----> | - +---+---+------+--------+ | | | +------------------------+ - | | | +--^--+ - | +--------------+ | - | | - | +-------------+ | - | | Table of | | - | | registered | | - | | filesystems | | - | +-------------+ +-------------+ | - +-------> entry +----> esp_vfs_t | | - index +-------------+ | structure | | - | | | | | - | | | + fd_offset +---+ - +-------------+ | | - +-------------+ + + +-------+---------------+ +------------------------+ + | FS id | Zero—based FD | +--------------------------> | + +---+---+------+--------+ | +------------------------+ + | | | + | +--------------+ + | + | +-------------+ + | | Table of | + | | registered | + | | filesystems | + | +-------------+ +-------------+ + +-------> entry +----> esp_vfs_t | + index +-------------+ | structure | + | | | | + | | | | + +-------------+ +-------------+ Standard IO streams (stdin, stdout, stderr) @@ -170,4 +166,3 @@ Such a design has the following consequences: - It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g. by doing ``stdin = fopen("/dev/uart/1", "r")``. - Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object — this will affect all other tasks. - To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task. - diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 6a8ed3306d..bd1cf322c2 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -64,10 +64,9 @@ extern "C" { * This structure should be filled with pointers to corresponding * FS driver functions. * - * If the FS implementation has an option to use certain offset for - * all file descriptors, this value should be passed into fd_offset - * field. Otherwise VFS component will translate all FDs to start - * at zero offset. + * VFS component will translate all FDs so that the filesystem implementation + * sees them starting at zero. The caller sees a global FD which is prefixed + * with an pre-filesystem-implementation. * * Some FS implementations expect some state (e.g. pointer to some structure) * to be passed in as a first argument. For these implementations, @@ -82,7 +81,6 @@ extern "C" { */ typedef struct { - int fd_offset; /*!< file descriptor offset, determined by the FS driver */ int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT, plus optionally ESP_VFS_FLAG_SHARED_FD_SPACE */ union { ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index f3624259b6..24fde2e306 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -140,9 +140,9 @@ static const vfs_entry_t* get_vfs_for_fd(int fd) static int translate_fd(const vfs_entry_t* vfs, int fd) { if (vfs->vfs.flags & ESP_VFS_FLAG_SHARED_FD_SPACE) { - return fd + vfs->vfs.fd_offset; + return fd; } else { - return (fd & VFS_FD_MASK) + vfs->vfs.fd_offset; + return fd & VFS_FD_MASK; } } @@ -255,8 +255,7 @@ int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) if (ret < 0) { return ret; } - assert(ret >= vfs->vfs.fd_offset); - return ret - vfs->vfs.fd_offset + (vfs->offset << VFS_INDEX_S); + return ret + (vfs->offset << VFS_INDEX_S); } ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index e477e0e84e..f958278d21 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -263,7 +263,6 @@ static int uart_fcntl(int fd, int cmd, va_list args) void esp_vfs_dev_uart_register() { esp_vfs_t vfs = { - .fd_offset = 0, .flags = ESP_VFS_FLAG_DEFAULT, .write = &uart_write, .open = &uart_open,