Allow VFS file descriptors in select()

This commit is contained in:
Roland Dobai 2018-02-19 14:14:02 +01:00
parent c36687aac6
commit 6852d653bd
26 changed files with 1176 additions and 129 deletions

View file

@ -0,0 +1,49 @@
// Copyright 2015-2016 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.
#ifndef _UART_SELECT_H_
#define _UART_SELECT_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/uart.h"
typedef enum {
UART_SELECT_READ_NOTIF,
UART_SELECT_WRITE_NOTIF,
UART_SELECT_ERROR_NOTIF,
} uart_select_notif_t;
typedef void (*uart_select_notif_callback_t)(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken);
/**
* @brief Set notification callback function for select() events
* @param uart_num UART port number
* @param uart_select_notif_callback callback function
*/
void uart_set_select_notif_callback(uart_port_t uart_num, uart_select_notif_callback_t uart_select_notif_callback);
/**
* @brief Get mutex guarding select() notifications
*/
portMUX_TYPE *uart_get_selectlock();
#ifdef __cplusplus
}
#endif
#endif //_UART_SELECT_H_

View file

@ -29,6 +29,7 @@
#include "soc/uart_struct.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/uart_select.h"
#define XOFF (char)0x13
#define XON (char)0x11
@ -100,12 +101,14 @@ typedef struct {
uint8_t tx_brk_flg; /*!< Flag to indicate to send a break signal in the end of the item sending procedure */
uint8_t tx_brk_len; /*!< TX break signal cycle length/number */
uint8_t tx_waiting_brk; /*!< Flag to indicate that TX FIFO is ready to send break signal after FIFO is empty, do not push data into TX FIFO right now.*/
uart_select_notif_callback_t uart_select_notif_callback; /*!< Notification about select() events */
} uart_obj_t;
static uart_obj_t *p_uart_obj[UART_NUM_MAX] = {0};
/* DRAM_ATTR is required to avoid UART array placed in flash, due to accessed from ISR */
static DRAM_ATTR uart_dev_t* const UART[UART_NUM_MAX] = {&UART0, &UART1, &UART2};
static portMUX_TYPE uart_spinlock[UART_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED};
static portMUX_TYPE uart_selectlock = portMUX_INITIALIZER_UNLOCKED;
esp_err_t uart_set_word_length(uart_port_t uart_num, uart_word_length_t data_bit)
{
@ -831,6 +834,11 @@ static void uart_rx_intr_handler_default(void *param)
uart_clear_intr_status(uart_num, UART_RXFIFO_TOUT_INT_CLR_M | UART_RXFIFO_FULL_INT_CLR_M);
uart_event.type = UART_DATA;
uart_event.size = rx_fifo_len;
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
}
p_uart->rx_stash_len = rx_fifo_len;
//If we fail to push data to ring buffer, we will have to stash the data, and send next time.
@ -889,15 +897,30 @@ static void uart_rx_intr_handler_default(void *param)
uart_reg->int_clr.rxfifo_ovf = 1;
UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]);
uart_event.type = UART_FIFO_OVF;
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
} else if(uart_intr_status & UART_BRK_DET_INT_ST_M) {
uart_reg->int_clr.brk_det = 1;
uart_event.type = UART_BREAK;
} else if(uart_intr_status & UART_FRM_ERR_INT_ST_M) {
uart_reg->int_clr.frm_err = 1;
uart_event.type = UART_FRAME_ERR;
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
} else if(uart_intr_status & UART_PARITY_ERR_INT_ST_M) {
uart_reg->int_clr.parity_err = 1;
uart_event.type = UART_PARITY_ERR;
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
} else if(uart_intr_status & UART_TX_BRK_DONE_INT_ST_M) {
UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]);
uart_reg->conf0.txd_brk = 0;
@ -1255,6 +1278,7 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b
p_uart_obj[uart_num]->tx_ring_buf = NULL;
p_uart_obj[uart_num]->tx_buf_size = 0;
}
p_uart_obj[uart_num]->uart_select_notif_callback = NULL;
} else {
ESP_LOGE(UART_TAG, "UART driver already installed");
return ESP_FAIL;
@ -1342,3 +1366,15 @@ esp_err_t uart_driver_delete(uart_port_t uart_num)
}
return ESP_OK;
}
void uart_set_select_notif_callback(uart_port_t uart_num, uart_select_notif_callback_t uart_select_notif_callback)
{
if (uart_num < UART_NUM_MAX && p_uart_obj[uart_num]) {
p_uart_obj[uart_num]->uart_select_notif_callback = (uart_select_notif_callback_t) uart_select_notif_callback;
}
}
portMUX_TYPE *uart_get_selectlock()
{
return &uart_selectlock;
}

View file

@ -38,6 +38,7 @@
#if LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
#include <sys/select.h>
#include <stddef.h> /* for size_t */
#include <string.h> /* for FD_ZERO */
@ -591,8 +592,10 @@ static inline int sendto(int s,const void *dataptr,size_t size,int flags,const s
{ return lwip_sendto_r(s,dataptr,size,flags,to,tolen); }
static inline int socket(int domain,int type,int protocol)
{ return lwip_socket(domain,type,protocol); }
#ifndef ESP_VFS_SELECT
static inline int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)
{ return lwip_select(maxfdp1,readset,writeset,exceptset,timeout); }
#endif /* ESP_VFS_SELECT */
static inline int ioctlsocket(int s,long cmd,void *argp)
{ return lwip_ioctl_r(s,cmd,argp); }
@ -645,8 +648,10 @@ static inline int sendto(int s,const void *dataptr,size_t size,int flags,const s
{ return lwip_sendto(s,dataptr,size,flags,to,tolen); }
static inline int socket(int domain,int type,int protocol)
{ return lwip_socket(domain,type,protocol); }
#ifndef ESP_VFS_SELECT
static inline int select(int maxfdp1,fd_set t*readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)
{ return lwip_select(maxfdp1,readset,writeset,exceptset,timeout); }
#endif /* ESP_VFS_SELECT */
static inline int ioctlsocket(int s,long cmd,void *argp)
{ return lwip_ioctl(s,cmd,argp); }

View file

@ -148,6 +148,10 @@ err_t sys_sem_new(sys_sem_t *sem, u8_t count);
/** Signals a semaphore
* @param sem the semaphore to signal */
void sys_sem_signal(sys_sem_t *sem);
/** Signals a semaphore (ISR version)
* @param sem the semaphore to signal
* @return non-zero if a higher priority task has been woken */
int sys_sem_signal_isr(sys_sem_t *sem);
/** Wait for a semaphore for the specified timeout
* @param sem the semaphore to wait for
* @param timeout timeout in milliseconds to wait (0 = wait forever)

View file

@ -38,9 +38,11 @@
#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"
#include "esp_vfs.h"
/* Enable all Espressif-only options */
@ -726,6 +728,7 @@
#define ESP_DHCP_TIMER 1
#define ESP_LWIP_LOGI(...) ESP_LOGI("lwip", __VA_ARGS__)
#define ESP_PING 1
#define ESP_VFS_SELECT 1
#if CONFIG_LWIP_IRAM_OPTIMIZATION
#define ESP_IRAM_ATTR IRAM_ATTR

View file

@ -133,6 +133,15 @@ sys_sem_signal(sys_sem_t *sem)
xSemaphoreGive(*sem);
}
/*-----------------------------------------------------------------------------------*/
// Signals a semaphore (from ISR)
int sys_sem_signal_isr(sys_sem_t *sem)
{
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(*sem, &woken);
return woken == pdTRUE;
}
/*-----------------------------------------------------------------------------------*/
/*
Blocks the thread while waiting for the semaphore to be

View file

@ -25,16 +25,8 @@
#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;
@ -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,
@ -53,6 +45,7 @@ void esp_vfs_lwip_sockets_register()
.read = &lwip_read_r,
.fcntl = &lwip_fcntl_r_wrapper,
.ioctl = &lwip_ioctl_r_wrapper,
.socket_select = &lwip_select
};
int max_fd;

View file

@ -0,0 +1,31 @@
// Copyright 2015-2016 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.
#ifndef __ESP_SYS_SELECT_H__
#define __ESP_SYS_SELECT_H__
#include <sys/types.h>
#include <sys/time.h>
#ifdef __cplusplus
extern "C" {
#endif
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__ESP_SYS_SELECT_H__

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

@ -0,0 +1,21 @@
// Copyright 2015-2016 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 <sys/select.h>
#include "esp_vfs.h"
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
{
return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout);
}

View file

@ -64,6 +64,35 @@ Case 2: API functions are declared with an extra context pointer (FS driver supp
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));
Synchronous input/output multiplexing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you want to use synchronous input/output multiplexing by :cpp:func:`select`
then you need to register the VFS with :cpp:func:`start_select` and
:cpp:func:`end_select` functions similarly to the following example:
.. highlight:: c
::
// In definition of esp_vfs_t:
.start_select = &uart_start_select,
.end_select = &uart_end_select,
// ... other members initialized
:cpp:func:`start_select` is called for setting up the environment for
detection of read/write/error conditions on file descriptors belonging to the
given VFS. :cpp:func:`end_select` is called to stop/deinitialize/free the
environment which was setup by :cpp:func:`start_select`. Please refer to the
reference implementation for the UART peripheral in
:component_file:`vfs/vfs_uart.c` and most particularly to functions
:cpp:func:`esp_vfs_dev_uart_register`, :cpp:func:`uart_start_select` and
:cpp:func:`uart_end_select`.
Examples demonstrating the use of :cpp:func:`select` with VFS file descriptors
are the :example:`peripherals/uart_select` and the :example:`system/select`
examples.
Paths
-----
@ -93,41 +122,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 ``fd_vfs`` for mapping file descriptors to VFS driver indexes registered in the ``s_vfs`` array.
Standard IO streams (stdin, stdout, stderr)
-------------------------------------------

View file

@ -18,11 +18,15 @@
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include <sys/types.h>
#include <sys/reent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <dirent.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
@ -43,20 +47,9 @@ 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
#if (FD_SETSIZE < CONFIG_LWIP_MAX_SOCKETS)
#error "FD_SETSIZE < CONFIG_LWIP_MAX_SOCKETS"
#endif
/**
* @brief VFS definition structure
@ -81,7 +74,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);
@ -166,6 +159,12 @@ typedef struct
int (*fsync_p)(void* ctx, int fd);
int (*fsync)(int fd);
};
/** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */
esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, void *callback_handle);
/** socket select function for socket FDs with similar functionality to POSIX select(); this should be set only for the socket VFS */
int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
/** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */
void (*end_select)();
} esp_vfs_t;
@ -199,8 +198,8 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct
*
* @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 p_min_fd If non-NULL, on success this variable is written with the minimum (global/user-facing) FD that this VFS will use.
* @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.
*
* @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
* registered.
@ -233,10 +232,55 @@ int esp_vfs_unlink(struct _reent *r, const char *path);
int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
/**@}*/
/**
* @brief Synchronous I/O multiplexing which implements the functionality of POSIX select() for VFS
* @param nfds Specifies the range of descriptors which should be checked.
* The first nfds descriptors will be checked in each set.
* @param readfds If not NULL, then points to a descriptor set that on input
* specifies which descriptors should be checked for being
* ready to read, and on output indicates which descriptors
* are ready to read.
* @param writefds If not NULL, then points to a descriptor set that on input
* specifies which descriptors should be checked for being
* ready to write, and on output indicates which descriptors
* are ready to write.
* @param errorfds If not NULL, then points to a descriptor set that on input
* specifies which descriptors should be checked for error
* conditions, and on output indicates which descriptors
* have error conditions.
* @param timeout If not NULL, then points to timeval structure which
* specifies the time period after which the functions should
* time-out and return. If it is NULL, then the function will
* not time-out.
*
* @return The number of descriptors set in the descriptor sets, or -1
* when an error (specified by errno) have occurred.
*/
int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
/**
* @brief Notification from a VFS driver about a read/write/error condition
*
* This function is called when the VFS driver detects a read/write/error
* condition as it was requested by the previous call to start_select.
*
* @param callback_handle Callback handle which was provided by start_select.
*/
void esp_vfs_select_triggered(void *callback_handle);
/**
* @brief Notification from a VFS driver about a read/write/error condition (ISR version)
*
* This function is called when the VFS driver detects a read/write/error
* condition as it was requested by the previous call to start_select.
*
* @param callback_handle Callback handle which was provided by start_select.
* @param woken should be set to pdTRUE if the function wakes up a task with higher priority
*/
void esp_vfs_select_triggered_isr(void *callback_handle, BaseType_t *woken);
#ifdef __cplusplus
} // extern "C"
#endif
#endif //__ESP_VFS_H__

View file

@ -0,0 +1,208 @@
// Copyright 2015-2016 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 <unistd.h>
#include <sys/fcntl.h>
#include <sys/param.h>
#include "unity.h"
#include "soc/uart_struct.h"
#include "freertos/FreeRTOS.h"
#include "driver/uart.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
typedef struct {
int fd;
int delay_ms;
} test_task_param_t;
static const char message[] = "Hello world!";
static int socket_init()
{
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_DGRAM,
};
struct addrinfo *res;
int err;
struct sockaddr_in saddr = { 0 };
int socket_fd = -1;
err = getaddrinfo("localhost", "80", &hints, &res);
TEST_ASSERT_EQUAL(err, 0);
TEST_ASSERT_NOT_NULL(res);
socket_fd = socket(res->ai_family, res->ai_socktype, 0);
TEST_ASSERT(socket_fd >= 0);
saddr.sin_family = PF_INET;
saddr.sin_port = htons(80);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
err = bind(socket_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
TEST_ASSERT(err >= 0);
err = connect(socket_fd, res->ai_addr, res->ai_addrlen);
TEST_ASSERT_EQUAL_MESSAGE(err, 0, "Socket connection failed");
freeaddrinfo(res);
return socket_fd;
}
static void uart1_init()
{
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM_1, &uart_config);
uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0);
}
static void send_task(void *param)
{
const test_task_param_t *test_task_param = param;
vTaskDelay(test_task_param->delay_ms / portTICK_PERIOD_MS);
write(test_task_param->fd, message, sizeof(message));
vTaskDelete(NULL);
}
static inline void start_task(const test_task_param_t *test_task_param)
{
xTaskCreate(send_task, "send_task", 4*1024, (void *) test_task_param, 5, NULL);
}
static void init(int *uart_fd, int *socket_fd)
{
uart1_init();
UART1.conf0.loopback = 1;
*uart_fd = open("/dev/uart/1", O_RDWR);
TEST_ASSERT_NOT_EQUAL_MESSAGE(*uart_fd, -1, "Cannot open UART");
esp_vfs_dev_uart_use_driver(*uart_fd);
*socket_fd = socket_init();
}
static void deinit(int uart_fd, int socket_fd)
{
esp_vfs_dev_uart_use_nonblocking(uart_fd);
close(uart_fd);
UART1.conf0.loopback = 0;
uart_driver_delete(UART_NUM_1);
close(socket_fd);
}
TEST_CASE("UART can do select()", "[vfs]")
{
int uart_fd;
int socket_fd;
struct timeval tv = {
.tv_sec = 0,
.tv_usec = 100000,
};
char recv_message[sizeof(message)];
init(&uart_fd, &socket_fd);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(uart_fd, &rfds);
FD_SET(socket_fd, &rfds);
const test_task_param_t test_task_param = {
.fd = uart_fd,
.delay_ms = 50,
};
start_task(&test_task_param);
const int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
TEST_ASSERT_EQUAL(s, 1);
TEST_ASSERT(FD_ISSET(uart_fd, &rfds));
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
int read_bytes = read(uart_fd, recv_message, sizeof(message));
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
deinit(uart_fd, socket_fd);
}
TEST_CASE("socket can do select()", "[vfs]")
{
int uart_fd;
int socket_fd;
struct timeval tv = {
.tv_sec = 0,
.tv_usec = 100000,
};
char recv_message[sizeof(message)];
init(&uart_fd, &socket_fd);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(uart_fd, &rfds);
FD_SET(socket_fd, &rfds);
const test_task_param_t test_task_param = {
.fd = socket_fd,
.delay_ms = 50,
};
start_task(&test_task_param);
const int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
TEST_ASSERT_EQUAL(s, 1);
TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
TEST_ASSERT(FD_ISSET(socket_fd, &rfds));
int read_bytes = read(socket_fd, recv_message, sizeof(message));
TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
deinit(uart_fd, socket_fd);
}
TEST_CASE("select() timeout", "[vfs]")
{
int uart_fd;
int socket_fd;
struct timeval tv = {
.tv_sec = 0,
.tv_usec = 100000,
};
init(&uart_fd, &socket_fd);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(uart_fd, &rfds);
FD_SET(socket_fd, &rfds);
const int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
TEST_ASSERT_EQUAL(s, 0);
TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
deinit(uart_fd, socket_fd);
}

View file

@ -19,31 +19,15 @@
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/unistd.h>
#include <sys/param.h>
#include <dirent.h>
#include "esp_vfs.h"
#include "esp_log.h"
#include "port/arch/sys_arch.h"
#include "lwip/sys.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 MIN_LWIP_FD (FD_SETSIZE - CONFIG_LWIP_MAX_SOCKETS)
#define LEN_PATH_PREFIX_IGNORED SIZE_MAX /* special length value for VFS which is never recognised by open() */
@ -55,10 +39,19 @@ typedef struct vfs_entry_ {
int offset; // index of this structure in s_vfs array
} vfs_entry_t;
typedef struct {
bool isset; // none or at least one bit is set in the following 3 fd sets
fd_set readfds;
fd_set writefds;
fd_set errorfds;
} fds_triple_t;
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 short fd_vfs[FD_SETSIZE] = { [0 ... FD_SETSIZE-1] = -1 };
static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_entry_index)
{
if (len != LEN_PATH_PREFIX_IGNORED) {
if ((len != 0 && len < 2) || (len > ESP_VFS_PATH_MAX)) {
@ -96,11 +89,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_entry_index) {
*vfs_entry_index = index;
}
return ESP_OK;
@ -108,12 +98,29 @@ 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)
{
return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, p_min_fd, p_max_fd);
int index = -1;
esp_err_t ret = esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, &index);
if (ret == ESP_OK) {
if (p_min_fd != NULL) {
*p_min_fd = MIN_LWIP_FD;
}
if (p_max_fd != NULL) {
*p_max_fd = FD_SETSIZE;
}
}
for (int i = MIN_LWIP_FD; i < FD_SETSIZE; ++i) {
fd_vfs[i] = index;
}
return ret;
}
esp_err_t esp_vfs_unregister(const char* base_path)
@ -126,27 +133,40 @@ 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;
// Delete all references from the FD lookup-table
for (int j = 0; j < FD_SETSIZE; ++j) {
if (fd_vfs[j] == i) {
fd_vfs[j] = -1;
}
}
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;
return (fd < FD_SETSIZE) && (fd >= 0);
}
static inline const vfs_entry_t* get_vfs_for_fd(int fd)
{
if (fd_valid(fd)) {
return get_vfs_for_index(fd_vfs[fd]);
} else {
return fd & VFS_FD_MASK;
return NULL;
}
}
@ -256,10 +276,15 @@ int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode)
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;
if (ret >= MIN_LWIP_FD) {
CHECK_AND_CALL(ret, r, vfs, close, ret);
__errno_r(r) = ENFILE;
return -1;
}
return ret + (vfs->offset << VFS_INDEX_S);
if (ret >= 0) {
fd_vfs[ret] = vfs->offset;
}
return ret;
}
ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size)
@ -269,9 +294,8 @@ ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size)
__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);
CHECK_AND_CALL(ret, r, vfs, write, fd, data, size);
return ret;
}
@ -282,9 +306,8 @@ off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode)
__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);
CHECK_AND_CALL(ret, r, vfs, lseek, fd, size, mode);
return ret;
}
@ -295,9 +318,8 @@ ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size)
__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);
CHECK_AND_CALL(ret, r, vfs, read, fd, dst, size);
return ret;
}
@ -309,9 +331,13 @@ int esp_vfs_close(struct _reent *r, int fd)
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
CHECK_AND_CALL(ret, r, vfs, close, local_fd);
CHECK_AND_CALL(ret, r, vfs, close, fd);
if (fd < MIN_LWIP_FD) {
fd_vfs[fd] = -1;
} // else { Leave alone socket FDs because sockets are not created by
// esp_vfs_open and therefore fd_vfs won't be set again.
//}
return ret;
}
@ -322,9 +348,8 @@ int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st)
__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);
CHECK_AND_CALL(ret, r, vfs, fstat, fd, st);
return ret;
}
@ -404,14 +429,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 +449,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 +462,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 +475,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 +491,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;
@ -513,11 +538,10 @@ int fcntl(int fd, int cmd, ...)
__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, fcntl, local_fd, cmd, args);
CHECK_AND_CALL(ret, r, vfs, fcntl, fd, cmd, args);
va_end(args);
return ret;
}
@ -530,11 +554,10 @@ int ioctl(int fd, int cmd, ...)
__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);
CHECK_AND_CALL(ret, r, vfs, ioctl, fd, cmd, args);
va_end(args);
return ret;
}
@ -547,8 +570,166 @@ int fsync(int fd)
__errno_r(r) = EBADF;
return -1;
}
int local_fd = translate_fd(vfs, fd);
int ret;
CHECK_AND_CALL(ret, r, vfs, fsync, local_fd);
CHECK_AND_CALL(ret, r, vfs, fsync, fd);
return ret;
}
static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple)
{
for (int i = 0; i < end_index; ++i) {
const vfs_entry_t *vfs = get_vfs_for_index(i);
const fds_triple_t *item = &vfs_fds_triple[i];
if (vfs && vfs->vfs.end_select && item->isset) {
vfs->vfs.end_select();
}
}
}
static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_set *readfds, fd_set *writefds, fd_set *errorfds)
{
int ret = 0;
for (int i = 0; i < size; ++i) {
const fds_triple_t *item = &vfs_fds_triple[i];
if (item->isset) {
for (int j = 0; j < sizeof(item->readfds.fds_bits)/sizeof(item->readfds.fds_bits[0]); ++j) {
if (readfds != NULL) {
readfds->fds_bits[j] |= item->readfds.fds_bits[j];
ret += __builtin_popcountl(item->readfds.fds_bits[j]);
}
if (writefds != NULL) {
writefds->fds_bits[j] |= item->writefds.fds_bits[j];
ret += __builtin_popcountl(item->writefds.fds_bits[j]);
}
if (errorfds != NULL) {
errorfds->fds_bits[j] |= item->errorfds.fds_bits[j];
ret += __builtin_popcountl(item->errorfds.fds_bits[j]);
}
}
}
}
return ret;
}
int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
{
const int non_socket_nfds = MIN(nfds, MIN_LWIP_FD);
int ret = 0;
struct _reent* r = __getreent();
if (nfds > FD_SETSIZE || nfds < 0) {
__errno_r(r) = EINVAL;
return -1;
}
fds_triple_t *vfs_fds_triple;
if ((vfs_fds_triple = calloc(s_vfs_count, sizeof(fds_triple_t))) == NULL) {
__errno_r(r) = ENOMEM;
return -1;
}
for (int i = 0; i < non_socket_nfds; ++i) {
if (fd_vfs[i] < 0) {
continue;
}
fds_triple_t *item = &vfs_fds_triple[fd_vfs[i]]; // FD sets for VFS which belongs to FD i
if (readfds && FD_ISSET(i, readfds)) {
item->isset = true;
FD_SET(i, &item->readfds);
FD_CLR(i, readfds);
}
if (writefds && FD_ISSET(i, writefds)) {
item->isset = true;
FD_SET(i, &item->writefds);
FD_CLR(i, writefds);
}
if (errorfds && FD_ISSET(i, errorfds)) {
item->isset = true;
FD_SET(i, &item->errorfds);
FD_CLR(i, errorfds);
}
}
// all non-socket VFSs have their FD sets in vfs_fds_triple
// the global readfds, writefds and errorfds contain only socket FDs
void *callback_handle = sys_thread_sem_get();
if (!callback_handle) {
__errno_r(r) = ENOMEM;
return -1;
}
sys_arch_sem_wait(callback_handle, 1); //ensure the semaphore is taken so we are not signalled out-of-sequence; 0 timeout would block forever
for (int i = 0; i < s_vfs_count; ++i) {
const vfs_entry_t *vfs = get_vfs_for_index(i);
fds_triple_t *item = &vfs_fds_triple[i];
if (vfs && vfs->vfs.start_select && item->isset) {
// call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds
// note: it can point to socket VFS but item->isset will be false for that
esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, callback_handle);
if (err != ESP_OK) {
call_end_selects(i, vfs_fds_triple);
(void) set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds);
free(vfs_fds_triple);
__errno_r(r) = ENOMEM;
return -1;
}
}
}
int socket_select_called = 0;
for (int i = MIN_LWIP_FD; i < FD_SETSIZE; ++i) {
const vfs_entry_t* vfs = get_vfs_for_fd(i);
if (vfs != NULL && vfs->vfs.socket_select != NULL) {
// find socket VFS and call socket_select
ret = vfs->vfs.socket_select(nfds, readfds, writefds, errorfds, timeout);
socket_select_called = 1;
break;
}
}
if (!socket_select_called) {
uint32_t timeout_ms = 0;
if (readfds) {
FD_ZERO(readfds);
}
if (writefds) {
FD_ZERO(writefds);
}
if (errorfds) {
FD_ZERO(errorfds);
}
if (timeout) {
timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
timeout_ms = MAX(timeout_ms, 1); //make sure it is not 0 which would be in infinite wait
}
sys_arch_sem_wait(callback_handle, timeout_ms);
sys_sem_free(callback_handle);
}
call_end_selects(s_vfs_count, vfs_fds_triple); // for VFSs for start_select was called before
if (ret >= 0) {
ret += set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds);
}
//don't free callback_handle because it is freed in lwip_select (vfs.socket_select)
free(vfs_fds_triple);
return ret;
}
void esp_vfs_select_triggered(void *callback_handle)
{
if (callback_handle) {
sys_sem_signal(callback_handle); //vfs->vfs.socket_select will return
}
}
void esp_vfs_select_triggered_isr(void *callback_handle, BaseType_t *woken)
{
if (callback_handle && sys_sem_signal_isr(callback_handle) && woken) {
*woken = pdTRUE;
}
}

View file

@ -24,6 +24,7 @@
#include "soc/uart_struct.h"
#include "driver/uart.h"
#include "sdkconfig.h"
#include "driver/uart_select.h"
// TODO: make the number of UARTs chip dependent
#define UART_NUM 3
@ -56,6 +57,14 @@ static int s_peek_char[UART_NUM] = { NONE, NONE, NONE };
// driver is used.
static bool s_non_blocking[UART_NUM];
static fd_set *_readfds = NULL;
static fd_set *_writefds = NULL;
static fd_set *_errorfds = NULL;
static fd_set *_readfds_orig = NULL;
static fd_set *_writefds_orig = NULL;
static fd_set *_errorfds_orig = NULL;
static void *_vfs_callback_handle = NULL;
// Newline conversion mode when transmitting
static esp_line_endings_t s_tx_mode =
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
@ -267,6 +276,111 @@ static int uart_fcntl(int fd, int cmd, va_list args)
return result;
}
static void select_notif_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken)
{
switch (uart_select_notif) {
case UART_SELECT_READ_NOTIF:
if (FD_ISSET(uart_num, _readfds_orig)) {
FD_SET(uart_num, _readfds);
esp_vfs_select_triggered_isr(_vfs_callback_handle, task_woken);
}
break;
case UART_SELECT_WRITE_NOTIF:
if (FD_ISSET(uart_num, _writefds_orig)) {
FD_SET(uart_num, _writefds);
esp_vfs_select_triggered_isr(_vfs_callback_handle, task_woken);
}
break;
case UART_SELECT_ERROR_NOTIF:
if (FD_ISSET(uart_num, _errorfds_orig)) {
FD_SET(uart_num, _errorfds);
esp_vfs_select_triggered_isr(_vfs_callback_handle, task_woken);
}
break;
}
}
static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, void *callback_handle)
{
const int max_fds = nfds < UART_NUM ? nfds : UART_NUM;
taskENTER_CRITICAL(uart_get_selectlock());
if (_readfds || _writefds || _errorfds || _readfds_orig || _writefds_orig || _errorfds_orig) {
taskEXIT_CRITICAL(uart_get_selectlock());
return ESP_ERR_INVALID_STATE;
}
if ((_readfds_orig = malloc(sizeof(fd_set))) == NULL) {
taskEXIT_CRITICAL(uart_get_selectlock());
return ESP_ERR_NO_MEM;
}
if ((_writefds_orig = malloc(sizeof(fd_set))) == NULL) {
taskEXIT_CRITICAL(uart_get_selectlock());
return ESP_ERR_NO_MEM;
}
if ((_errorfds_orig = malloc(sizeof(fd_set))) == NULL) {
taskEXIT_CRITICAL(uart_get_selectlock());
return ESP_ERR_NO_MEM;
}
_vfs_callback_handle = callback_handle;
//uart_set_select_notif_callback set the callbacks in UART ISR
for (int i = 0; i < max_fds; ++i) {
if (FD_ISSET(i, readfds) || FD_ISSET(i, writefds) || FD_ISSET(i, exceptfds)) {
uart_set_select_notif_callback(i, select_notif_callback);
}
}
_readfds = readfds;
_writefds = writefds;
_errorfds = exceptfds;
*_readfds_orig = *readfds;
*_writefds_orig = *writefds;
*_errorfds_orig = *exceptfds;
FD_ZERO(readfds);
FD_ZERO(writefds);
FD_ZERO(exceptfds);
taskEXIT_CRITICAL(uart_get_selectlock());
return ESP_OK;
}
static void uart_end_select()
{
taskENTER_CRITICAL(uart_get_selectlock());
_vfs_callback_handle = NULL;
for (int i = 0; i < UART_NUM; ++i) {
uart_set_select_notif_callback(i, NULL);
}
_readfds = NULL;
_writefds = NULL;
_errorfds = NULL;
if (_readfds_orig) {
free(_readfds_orig);
_readfds_orig = NULL;
}
if (_writefds_orig) {
free(_writefds_orig);
_writefds_orig = NULL;
}
if (_errorfds_orig) {
free(_errorfds_orig);
_errorfds_orig = NULL;
}
taskEXIT_CRITICAL(uart_get_selectlock());
}
void esp_vfs_dev_uart_register()
{
esp_vfs_t vfs = {
@ -276,7 +390,9 @@ void esp_vfs_dev_uart_register()
.fstat = &uart_fstat,
.close = &uart_close,
.read = &uart_read,
.fcntl = &uart_fcntl
.fcntl = &uart_fcntl,
.start_select = &uart_start_select,
.end_select = &uart_end_select,
};
ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL));
}

View file

@ -142,6 +142,8 @@ Demonstration of how to report various communication events and how to use pater
Transmitting and receiveing with the same UART in two separate FreeRTOS tasks: :example:`peripherals/uart_async_rxtxtasks`.
Using synchronous I/O multiplexing for UART file descriptors: :example:`peripherals/uart_select`.
API Reference
-------------

View file

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := uart_select
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,13 @@
# UART Select Example
The UART select example is for demonstrating the use of `select()` for
synchronous I/O multiplexing on the UART interface. The example waits for a
character from UART using `select()` until a blocking read without delay or a
successful non-blocking read is possible.
Please note that the same result can be achieved by using `uart_read_bytes()`
but the use of `select()` allows to use it together with other virtual
file system (VFS) drivers, e.g. LWIP sockets. For a more comprehensive example
please refer to `system/select`.
See the README.md file in the upper level 'examples' directory for more information about examples.

View file

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View file

@ -0,0 +1,90 @@
/* UART Select Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/unistd.h>
#include <sys/select.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
static const char* TAG = "uart_select_example";
static void uart_select_task()
{
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM_0, &uart_config);
uart_driver_install(UART_NUM_0, 2*1024, 0, 0, NULL, 0);
while (1) {
int fd;
if ((fd = open("/dev/uart/0", O_RDWR)) == -1) {
ESP_LOGE(TAG, "Cannot open UART");
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
// We have a driver now installed so set up the read/write functions to use driver also.
esp_vfs_dev_uart_use_driver(fd);
while (1) {
int s;
fd_set rfds;
struct timeval tv = {
.tv_sec = 5,
.tv_usec = 0,
};
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
s = select(fd + 1, &rfds, NULL, NULL, &tv);
if (s < 0) {
ESP_LOGE(TAG, "Select failed: errno %d", errno);
break;
} else if (s == 0) {
ESP_LOGI(TAG, "Timeout has been reached and nothing has been received");
} else {
if (FD_ISSET(fd, &rfds)) {
char buf;
if (read(fd, &buf, 1) > 0) {
ESP_LOGI(TAG, "Received: %c", buf);
} else {
ESP_LOGE(TAG, "UART read error");
break;
}
} else {
ESP_LOGE(TAG, "No FD has been set in select()");
break;
}
}
}
close(fd);
}
vTaskDelete(NULL);
}
void app_main()
{
xTaskCreate(uart_select_task, "uart_select_task", 4*1024, NULL, 5, NULL);
}

View file

@ -420,7 +420,7 @@ static void mcast_example_task(void *pvParameters)
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
int s = lwip_select(sock + 1, &rfds, NULL, NULL, &tv);
int s = select(sock + 1, &rfds, NULL, NULL, &tv);
if (s < 0) {
ESP_LOGE(TAG, "Select failed: errno %d", errno);
err = -1;

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := select-example
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,11 @@
# Synchronous I/O multiplexing example
The example demonstrates the use of synchronous I/O multiplexing by the select()
function with UART and socket file descriptors. The example starts three tasks:
1. The first task writes periodically to the UART1 file descriptor.
2. The second task writes periodically to the socket descriptor.
3. Both UART1 and the socket are configured to act as loopbacks. The third
task detects by the use of select() whether it is possible to read from
UART1 or the socket, and receives the sent messages from the other tasks.
See the README.md file in the upper level 'examples' directory for more information about examples.

View file

@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View file

@ -0,0 +1,207 @@
/* Hello World Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/unistd.h>
#include <sys/param.h>
#include <sys/select.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "tcpip_adapter.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
static const char* TAG = "uart_select_example";
static int uart_fd = -1;
static int socket_fd = -1;
static void socket_deinit()
{
close(socket_fd);
socket_fd = -1;
}
static void socket_init()
{
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_DGRAM,
};
struct addrinfo *res;
int err;
struct sockaddr_in saddr = { 0 };
tcpip_adapter_init();
err = getaddrinfo("localhost", "80", &hints, &res);
if (err != 0 || res == NULL) {
ESP_LOGE(TAG, "DNS lookup failed: %d", errno);
return;
}
socket_fd = socket(res->ai_family, res->ai_socktype, 0);
if (socket_fd < 0) {
ESP_LOGE(TAG, "Failed to allocate socket.");
freeaddrinfo(res);
return;
}
saddr.sin_family = PF_INET;
saddr.sin_port = htons(80);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
err = bind(socket_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
if (err < 0) {
ESP_LOGE(TAG, "Failed to bind socket");
freeaddrinfo(res);
socket_deinit();
return;
}
if (connect(socket_fd, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(TAG, "Socket connection failed: %d", errno);
freeaddrinfo(res);
socket_deinit();
return;
}
freeaddrinfo(res);
}
static void uart1_deinit()
{
close(uart_fd);
uart_fd = -1;
uart_driver_delete(UART_NUM_1);
UART1.conf0.loopback = 0;
}
static void uart1_init()
{
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM_1, &uart_config);
uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0);
UART1.conf0.loopback = 1;
if ((uart_fd = open("/dev/uart/1", O_RDWR | O_NONBLOCK)) == -1) {
ESP_LOGE(TAG, "Cannot open UART1");
uart1_deinit();
}
esp_vfs_dev_uart_use_driver(uart_fd);
}
static void uart1_write_task(void *param)
{
char buf[20];
uart1_init();
for (uint8_t i = 1;; ++i) {
vTaskDelay(4000 / portTICK_PERIOD_MS);
snprintf(buf, sizeof(buf), "UART message U%d", i);
int write_bytes = write(uart_fd, buf, strlen(buf));
if (write_bytes < 0) {
ESP_LOGE(TAG, "UART1 write error");
} else {
ESP_LOGI(TAG, "%d bytes were sent to UART1: %s", write_bytes, buf);
}
}
uart1_deinit(uart_fd);
vTaskDelete(NULL);
}
static void socket_write_task(void *param)
{
char buf[20];
socket_init();
for (uint8_t i = 1;; ++i) {
vTaskDelay(3000 / portTICK_PERIOD_MS);
snprintf(buf, sizeof(buf), "Socket message S%d", i);
int write_bytes = write(socket_fd, buf, strlen(buf));
if (write_bytes < 0) {
ESP_LOGE(TAG, "Socket write error");
} else {
ESP_LOGI(TAG, "%d bytes were written to socket: %s", write_bytes, buf);
}
}
socket_deinit();
vTaskDelete(NULL);
}
static void check_and_print(int fd, const fd_set *rfds, const char *src_msg)
{
char buf[100];
int read_bytes;
if (FD_ISSET(fd, rfds)) {
if ((read_bytes = read(fd, buf, sizeof(buf)-1)) > 0) {
buf[read_bytes] = '\0';
ESP_LOGI(TAG, "%d bytes were received through %s: %s", read_bytes, src_msg, buf);
} else {
ESP_LOGE(TAG, "%s read error", src_msg);
}
}
}
static void select_task(void *param)
{
while (1) {
int s;
fd_set rfds;
struct timeval tv = {
.tv_sec = 1,
.tv_usec = 0,
};
FD_ZERO(&rfds);
FD_SET(uart_fd, &rfds);
FD_SET(socket_fd, &rfds);
s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
if (s < 0) {
ESP_LOGE(TAG, "Select failed: errno %d", errno);
} else if (s == 0) {
ESP_LOGI(TAG, "Timeout has been reached and nothing has been received");
} else {
check_and_print(socket_fd, &rfds, "socket");
check_and_print(uart_fd, &rfds, "UART1");
}
}
vTaskDelete(NULL);
}
void app_main()
{
xTaskCreate(uart1_write_task, "uart1_write_task", 4*1024, NULL, 5, NULL);
xTaskCreate(socket_write_task, "socket_write_task", 4*1024, NULL, 5, NULL);
vTaskDelay(1000 / portTICK_PERIOD_MS);
xTaskCreate(select_task, "select_task", 4*1024, NULL, 5, NULL);
}

View file

@ -3,6 +3,7 @@
#include "freertos/task.h"
#include "unity.h"
#include "unity_config.h"
#include "tcpip_adapter.h"
void unityTask(void *pvParameters)
{
@ -12,6 +13,10 @@ void unityTask(void *pvParameters)
void app_main()
{
// TCP/IP adapter is initialized here because it leaks memory so the
// initialization in test cases would make the test fail because of leak.
tcpip_adapter_init();
// Note: if unpinning this task, change the way run times are calculated in
// unity_platform
xTaskCreatePinnedToCore(unityTask, "unityTask", 8192, NULL,