kopia lustrzana https://github.com/espressif/esp-idf
Merge branch 'feature/vfs_select' into 'master'
Allow VFS file descriptors in select() See merge request idf/esp-idf!2377pull/1979/head
commit
3c457afca5
components
driver
include/driver
lwip
port
freertos
vfs
docs/en/api-reference/peripherals
examples
peripherals/uart_select
protocols/udp_multicast/main
system/select
tools/unit-test-app/main
|
@ -0,0 +1,49 @@
|
|||
|
||||
// Copyright 2018 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_
|
|
@ -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)
|
||||
{
|
||||
|
@ -835,6 +838,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.
|
||||
|
@ -893,15 +901,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;
|
||||
|
@ -1259,6 +1282,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;
|
||||
|
@ -1346,3 +1370,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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
@ -589,8 +590,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_PLATFORM
|
||||
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_PLATFORM */
|
||||
static inline int ioctlsocket(int s,long cmd,void *argp)
|
||||
{ return lwip_ioctl_r(s,cmd,argp); }
|
||||
|
||||
|
@ -643,8 +646,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_PLATFORM
|
||||
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_PLATFORM */
|
||||
static inline int ioctlsocket(int s,long cmd,void *argp)
|
||||
{ return lwip_ioctl(s,cmd,argp); }
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,15 +24,31 @@
|
|||
#include "soc/uart_struct.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/* Non-LWIP file descriptors are from 0 to (LWIP_SOCKET_OFFSET-1).
|
||||
* LWIP file descriptors are from LWIP_SOCKET_OFFSET to FD_SETSIZE-1.
|
||||
*/
|
||||
#include "lwip/sys.h"
|
||||
|
||||
_Static_assert(MAX_FDS >= CONFIG_LWIP_MAX_SOCKETS, "MAX_FDS < CONFIG_LWIP_MAX_SOCKETS");
|
||||
|
||||
static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args);
|
||||
static int lwip_ioctl_r_wrapper(int fd, int cmd, va_list args);
|
||||
static void lwip_stop_socket_select()
|
||||
{
|
||||
sys_sem_signal(sys_thread_sem_get()); //socket_select will return
|
||||
}
|
||||
|
||||
static void lwip_stop_socket_select_isr(BaseType_t *woken)
|
||||
{
|
||||
if (sys_sem_signal_isr(sys_thread_sem_get()) && woken) {
|
||||
*woken = pdTRUE;
|
||||
}
|
||||
}
|
||||
|
||||
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 *));
|
||||
}
|
||||
|
||||
void esp_vfs_lwip_sockets_register()
|
||||
{
|
||||
|
@ -45,17 +61,14 @@ void esp_vfs_lwip_sockets_register()
|
|||
.read = &lwip_read_r,
|
||||
.fcntl = &lwip_fcntl_r_wrapper,
|
||||
.ioctl = &lwip_ioctl_r_wrapper,
|
||||
.socket_select = &lwip_select,
|
||||
.stop_socket_select = &lwip_stop_socket_select,
|
||||
.stop_socket_select_isr = &lwip_stop_socket_select_isr,
|
||||
};
|
||||
/* Non-LWIP file descriptors are from 0 to (LWIP_SOCKET_OFFSET-1). LWIP
|
||||
* file descriptors are registered from LWIP_SOCKET_OFFSET to
|
||||
* MAX_FDS-1.
|
||||
*/
|
||||
|
||||
ESP_ERROR_CHECK(esp_vfs_register_fd_range(&vfs, NULL, LWIP_SOCKET_OFFSET, MAX_FDS));
|
||||
}
|
||||
|
||||
static int lwip_fcntl_r_wrapper(int fd, int cmd, va_list args)
|
||||
{
|
||||
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 *));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2018 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__
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2018 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);
|
||||
}
|
|
@ -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
|
||||
-----
|
||||
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
#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" {
|
||||
|
@ -167,6 +169,16 @@ typedef struct
|
|||
int (*access_p)(void* ctx, const char *path, int amode);
|
||||
int (*access)(const char *path, int amode);
|
||||
};
|
||||
/** 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);
|
||||
/** socket select function for socket FDs with the functionality of 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);
|
||||
/** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */
|
||||
void (*stop_socket_select)();
|
||||
/** stop_socket_select which can be called from ISR; set only for the socket driver */
|
||||
void (*stop_socket_select_isr)(BaseType_t *woken);
|
||||
/** 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;
|
||||
|
||||
|
||||
|
@ -235,6 +247,50 @@ 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.
|
||||
*/
|
||||
void esp_vfs_select_triggered();
|
||||
|
||||
/**
|
||||
* @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 woken is set to pdTRUE if the function wakes up a task with higher priority
|
||||
*/
|
||||
void esp_vfs_select_triggered_isr(BaseType_t *woken);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
// Copyright 2018 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;
|
||||
xSemaphoreHandle sem;
|
||||
} 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));
|
||||
if (test_task_param->sem) {
|
||||
xSemaphoreGive(test_task_param->sem);
|
||||
}
|
||||
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(1);
|
||||
|
||||
*socket_fd = socket_init();
|
||||
}
|
||||
|
||||
static void deinit(int uart_fd, int socket_fd)
|
||||
{
|
||||
esp_vfs_dev_uart_use_nonblocking(1);
|
||||
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);
|
||||
//without socket in rfds it will not use the same signalization
|
||||
|
||||
const test_task_param_t test_task_param = {
|
||||
.fd = uart_fd,
|
||||
.delay_ms = 50,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
start_task(&test_task_param);
|
||||
|
||||
int s = select(uart_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));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(uart_fd, &rfds);
|
||||
FD_SET(socket_fd, &rfds);
|
||||
|
||||
start_task(&test_task_param);
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
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,
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
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));
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void select_task(void *param)
|
||||
{
|
||||
const test_task_param_t *test_task_param = param;
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,
|
||||
};
|
||||
|
||||
int s = select(1, NULL, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, 0); //timeout
|
||||
|
||||
if (test_task_param->sem) {
|
||||
xSemaphoreGive(test_task_param->sem);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("concurent select() fails", "[vfs]")
|
||||
{
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 100000,//irrelevant
|
||||
};
|
||||
const test_task_param_t test_task_param = {
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(test_task_param.sem);
|
||||
xTaskCreate(select_task, "select_task", 4*1024, (void *) &test_task_param, 5, NULL);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS); //make sure the task has started and waits in select()
|
||||
|
||||
int s = select(1, NULL, NULL, NULL, &tv);
|
||||
TEST_ASSERT_EQUAL(s, -1); //this select should fail because the other one is "waiting"
|
||||
TEST_ASSERT_EQUAL(errno, EINTR);
|
||||
|
||||
TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
|
||||
vSemaphoreDelete(test_task_param.sem);
|
||||
}
|
|
@ -20,7 +20,10 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/param.h>
|
||||
#include <dirent.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
@ -49,12 +52,28 @@ 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 fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED };
|
||||
static _lock_t s_fd_table_lock;
|
||||
|
||||
/* Semaphore used for waiting select events from other VFS drivers when socket
|
||||
* select is not used (not registered or socket FDs are not observed by the
|
||||
* given call of select)
|
||||
*/
|
||||
static SemaphoreHandle_t s_select_sem = NULL;
|
||||
|
||||
/* Lock ensuring that select is called from only one task at the time */
|
||||
static _lock_t s_one_select_lock;
|
||||
|
||||
static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index)
|
||||
{
|
||||
if (len != LEN_PATH_PREFIX_IGNORED) {
|
||||
|
@ -637,3 +656,214 @@ int access(const char *path, int amode)
|
|||
CHECK_AND_CALL(ret, r, vfs, access, path_within_vfs, amode);
|
||||
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 fd = 0; fd < MAX_FDS; ++fd) {
|
||||
const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
|
||||
if (readfds && FD_ISSET(local_fd, &item->readfds)) {
|
||||
FD_SET(fd, readfds);
|
||||
++ret;
|
||||
}
|
||||
if (writefds && FD_ISSET(local_fd, &item->writefds)) {
|
||||
FD_SET(fd, writefds);
|
||||
++ret;
|
||||
}
|
||||
if (errorfds && FD_ISSET(local_fd, &item->errorfds)) {
|
||||
FD_SET(fd, errorfds);
|
||||
++ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
|
||||
{
|
||||
int ret = 0;
|
||||
struct _reent* r = __getreent();
|
||||
|
||||
if (nfds > MAX_FDS || nfds < 0) {
|
||||
__errno_r(r) = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_lock_try_acquire(&s_one_select_lock)) {
|
||||
__errno_r(r) = EINTR;
|
||||
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;
|
||||
_lock_release(&s_one_select_lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int (*socket_select)(int, fd_set *, fd_set *, fd_set *, struct timeval *) = NULL;
|
||||
for (int fd = 0; fd < nfds; ++fd) {
|
||||
_lock_acquire(&s_fd_table_lock);
|
||||
const bool is_socket_fd = s_fd_table[fd].permanent;
|
||||
const int vfs_index = s_fd_table[fd].vfs_index;
|
||||
const int local_fd = s_fd_table[fd].local_fd;
|
||||
_lock_release(&s_fd_table_lock);
|
||||
|
||||
if (vfs_index < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!socket_select && is_socket_fd) {
|
||||
// no socket_select found yet and the fd is for a socket so take a look
|
||||
if ((readfds && FD_ISSET(fd, readfds)) ||
|
||||
(writefds && FD_ISSET(fd, writefds)) ||
|
||||
(errorfds && FD_ISSET(fd, errorfds))) {
|
||||
const vfs_entry_t *vfs = s_vfs[vfs_index];
|
||||
socket_select = vfs->vfs.socket_select;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fds_triple_t *item = &vfs_fds_triple[vfs_index]; // FD sets for VFS which belongs to fd
|
||||
if (readfds && FD_ISSET(fd, readfds)) {
|
||||
item->isset = true;
|
||||
FD_SET(local_fd, &item->readfds);
|
||||
FD_CLR(fd, readfds);
|
||||
}
|
||||
if (writefds && FD_ISSET(fd, writefds)) {
|
||||
item->isset = true;
|
||||
FD_SET(local_fd, &item->writefds);
|
||||
FD_CLR(fd, writefds);
|
||||
}
|
||||
if (errorfds && FD_ISSET(fd, errorfds)) {
|
||||
item->isset = true;
|
||||
FD_SET(local_fd, &item->errorfds);
|
||||
FD_CLR(fd, errorfds);
|
||||
}
|
||||
}
|
||||
|
||||
// all non-socket VFSs have their FD sets in vfs_fds_triple
|
||||
// the global readfds, writefds and errorfds contain only socket FDs (if
|
||||
// there any)
|
||||
|
||||
if (!socket_select) {
|
||||
// There is no socket VFS registered or select() wasn't called for
|
||||
// any socket. Therefore, we will use our own signalization.
|
||||
if ((s_select_sem = xSemaphoreCreateBinary()) == NULL) {
|
||||
free(vfs_fds_triple);
|
||||
__errno_r(r) = ENOMEM;
|
||||
_lock_release(&s_one_select_lock);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
if (s_select_sem) {
|
||||
vSemaphoreDelete(s_select_sem);
|
||||
s_select_sem = NULL;
|
||||
}
|
||||
free(vfs_fds_triple);
|
||||
__errno_r(r) = ENOMEM;
|
||||
_lock_release(&s_one_select_lock);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (socket_select) {
|
||||
ret = socket_select(nfds, readfds, writefds, errorfds, timeout);
|
||||
} else {
|
||||
if (readfds) {
|
||||
FD_ZERO(readfds);
|
||||
}
|
||||
if (writefds) {
|
||||
FD_ZERO(writefds);
|
||||
}
|
||||
if (errorfds) {
|
||||
FD_ZERO(errorfds);
|
||||
}
|
||||
|
||||
TickType_t ticks_to_wait = portMAX_DELAY;
|
||||
if (timeout) {
|
||||
uint32_t timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
|
||||
ticks_to_wait = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
xSemaphoreTake(s_select_sem, ticks_to_wait);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (s_select_sem) {
|
||||
vSemaphoreDelete(s_select_sem);
|
||||
s_select_sem = NULL;
|
||||
}
|
||||
free(vfs_fds_triple);
|
||||
_lock_release(&s_one_select_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void esp_vfs_select_triggered()
|
||||
{
|
||||
if (s_select_sem) {
|
||||
xSemaphoreGive(s_select_sem);
|
||||
} else {
|
||||
// Another way would be to go through s_fd_table and find the VFS
|
||||
// which has a permanent FD. But in order to avoid to lock
|
||||
// s_fd_table_lock we go through the VFS table.
|
||||
for (int i = 0; i < s_vfs_count; ++i) {
|
||||
const vfs_entry_t *vfs = s_vfs[i];
|
||||
if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) {
|
||||
vfs->vfs.stop_socket_select();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void esp_vfs_select_triggered_isr(BaseType_t *woken)
|
||||
{
|
||||
if (s_select_sem) {
|
||||
xSemaphoreGiveFromISR(s_select_sem, woken);
|
||||
} else {
|
||||
// Another way would be to go through s_fd_table and find the VFS
|
||||
// which has a permanent FD. But in order to avoid to lock
|
||||
// s_fd_table_lock we go through the VFS table.
|
||||
for (int i = 0; i < s_vfs_count; ++i) {
|
||||
const vfs_entry_t *vfs = s_vfs[i];
|
||||
if (vfs != NULL && vfs->vfs.stop_socket_select_isr != NULL) {
|
||||
vfs->vfs.stop_socket_select_isr(woken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
#include <sys/errno.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_attr.h"
|
||||
#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 +58,13 @@ 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;
|
||||
|
||||
// Newline conversion mode when transmitting
|
||||
static esp_line_endings_t s_tx_mode =
|
||||
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
|
||||
|
@ -288,6 +297,108 @@ static int uart_access(const char *path, int amode)
|
|||
return ret;
|
||||
}
|
||||
|
||||
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(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(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(task_woken);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds)
|
||||
{
|
||||
const int max_fds = MIN(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;
|
||||
}
|
||||
|
||||
//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());
|
||||
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 = {
|
||||
|
@ -299,6 +410,8 @@ void esp_vfs_dev_uart_register()
|
|||
.read = &uart_read,
|
||||
.fcntl = &uart_fcntl,
|
||||
.access = &uart_access,
|
||||
.start_select = &uart_start_select,
|
||||
.end_select = &uart_end_select,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
-------------
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -0,0 +1,3 @@
|
|||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
|
@ -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(0);
|
||||
|
||||
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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -0,0 +1,5 @@
|
|||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
/* 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/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(1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
|
@ -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,
|
||||
|
|
Ładowanie…
Reference in New Issue