From 3ebf7923d3d4370bc5e8a823fd1ae209bb2b8bc3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Oct 2017 16:56:55 +1100 Subject: [PATCH] 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