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
This commit is contained in:
Angus Gratton 2017-10-03 16:56:55 +11:00 committed by Angus Gratton
parent a320fed3b5
commit 3ebf7923d3
7 changed files with 193 additions and 11 deletions

View file

@ -37,6 +37,7 @@
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "arch/vfs_lwip.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {

View file

@ -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

View file

@ -34,6 +34,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <unistd.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/fcntl.h> #include <sys/fcntl.h>
#include "esp_task.h" #include "esp_task.h"
@ -687,6 +688,19 @@
#define ETHARP_TRUST_IP_MAC CONFIG_LWIP_ETHARP_TRUST_IP_MAC #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 */ /* Enable all Espressif-only options */
#define ESP_LWIP 1 #define ESP_LWIP 1

View file

@ -433,6 +433,7 @@ sys_init(void)
if (ERR_OK != sys_mutex_new(&g_lwip_protect_mutex)) { if (ERR_OK != sys_mutex_new(&g_lwip_protect_mutex)) {
ESP_LOGE(TAG, "sys_init: failed to init lwip protect mutex\n"); ESP_LOGE(TAG, "sys_init: failed to init lwip protect mutex\n");
} }
esp_vfs_lwip_sockets_register();
} }
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/

View file

@ -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 <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <sys/errno.h>
#include <sys/lock.h>
#include <sys/fcntl.h>
#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 *));
}

View file

@ -43,6 +43,21 @@ extern "C" {
*/ */
#define ESP_VFS_FLAG_CONTEXT_PTR 1 #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 * @brief VFS definition structure
* *
@ -68,7 +83,7 @@ extern "C" {
typedef struct typedef struct
{ {
int fd_offset; /*!< file descriptor offset, determined by the FS driver */ 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 { union {
ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); 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); 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); 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 * Unregister a virtual filesystem for given path prefix
* *

View file

@ -41,6 +41,8 @@
#define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS) #define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS)
#define VFS_INDEX_S 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_ { typedef struct vfs_entry_ {
esp_vfs_t vfs; // contains pointers to VFS functions esp_vfs_t vfs; // contains pointers to VFS functions
char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS 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 vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 };
static size_t s_vfs_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 != LEN_PATH_PREFIX_IGNORED) {
if ((len != 0 && len < 2)|| len > ESP_VFS_PATH_MAX) { if ((len != 0 && len < 2) || (len > ESP_VFS_PATH_MAX)) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
if ((len > 0 && base_path[0] != '/') || base_path[len - 1] == '/') { if ((len > 0 && base_path[0] != '/') || base_path[len - 1] == '/') {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
}
} }
vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t));
if (entry == NULL) { 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_count;
} }
s_vfs[index] = entry; 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)); memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t));
entry->path_prefix_len = len; entry->path_prefix_len = len;
entry->ctx = ctx; entry->ctx = ctx;
entry->offset = index; 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; 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) esp_err_t esp_vfs_unregister(const char* base_path)
{ {
for (size_t i = 0; i < s_vfs_count; ++i) { 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) 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) 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); size_t len = strlen(path);
for (size_t i = 0; i < s_vfs_count; ++i) { for (size_t i = 0; i < s_vfs_count; ++i) {
const vfs_entry_t* vfs = s_vfs[i]; const vfs_entry_t* vfs = s_vfs[i];
if (!vfs) { if (!vfs || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) {
continue; continue;
} }
// match path prefix // match path prefix