From: Roland Dobai Date: Thu, 3 May 2018 08:41:10 +0000 (+0200) Subject: Allow VFS file descriptors in select() X-Git-Tag: v3.1-beta1~122^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=18e83bcd53f2aacd667959af755f8a443fb2a57e;p=esp-idf Allow VFS file descriptors in select() --- diff --git a/components/driver/include/driver/uart_select.h b/components/driver/include/driver/uart_select.h new file mode 100644 index 0000000000..24f06c1de6 --- /dev/null +++ b/components/driver/include/driver/uart_select.h @@ -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_ diff --git a/components/driver/uart.c b/components/driver/uart.c index 9cec59bf08..e32023047e 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -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; +} diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index c3cadd8730..5978b38c49 100755 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -38,6 +38,7 @@ #if LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */ +#include #include /* for size_t */ #include /* 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); } diff --git a/components/lwip/include/lwip/lwip/sys.h b/components/lwip/include/lwip/lwip/sys.h index 86d0f3b336..67729e3b73 100755 --- a/components/lwip/include/lwip/lwip/sys.h +++ b/components/lwip/include/lwip/lwip/sys.h @@ -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) diff --git a/components/lwip/port/freertos/sys_arch.c b/components/lwip/port/freertos/sys_arch.c index 77eaef0a11..2c56969ade 100755 --- a/components/lwip/port/freertos/sys_arch.c +++ b/components/lwip/port/freertos/sys_arch.c @@ -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 diff --git a/components/lwip/port/vfs_lwip.c b/components/lwip/port/vfs_lwip.c index 2ce639e4e4..54d71912bb 100644 --- a/components/lwip/port/vfs_lwip.c +++ b/components/lwip/port/vfs_lwip.c @@ -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 *)); -} diff --git a/components/newlib/include/sys/select.h b/components/newlib/include/sys/select.h new file mode 100644 index 0000000000..199d48144d --- /dev/null +++ b/components/newlib/include/sys/select.h @@ -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 +#include + +#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__ diff --git a/components/newlib/select.c b/components/newlib/select.c new file mode 100644 index 0000000000..dc945eafb4 --- /dev/null +++ b/components/newlib/select.c @@ -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 +#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); +} diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 7a25ca542d..4644689a93 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -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 ----- diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 202be24859..d063dba0ac 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #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 diff --git a/components/vfs/test/test_vfs_select.c b/components/vfs/test/test_vfs_select.c new file mode 100644 index 0000000000..5febf39a47 --- /dev/null +++ b/components/vfs/test/test_vfs_select.c @@ -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 +#include +#include +#include +#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); +} diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index d7ca283ec3..7ea36b0391 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -20,7 +20,10 @@ #include #include #include +#include #include +#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; + } + } + } +} diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index 83e89acc34..c1c12066cb 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -18,12 +18,14 @@ #include #include #include +#include #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)); } diff --git a/docs/en/api-reference/peripherals/uart.rst b/docs/en/api-reference/peripherals/uart.rst index d7c82c37ed..ea2e9615b7 100644 --- a/docs/en/api-reference/peripherals/uart.rst +++ b/docs/en/api-reference/peripherals/uart.rst @@ -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 ------------- diff --git a/examples/peripherals/uart_select/Makefile b/examples/peripherals/uart_select/Makefile new file mode 100644 index 0000000000..010458f74c --- /dev/null +++ b/examples/peripherals/uart_select/Makefile @@ -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 diff --git a/examples/peripherals/uart_select/README.md b/examples/peripherals/uart_select/README.md new file mode 100644 index 0000000000..966e98644e --- /dev/null +++ b/examples/peripherals/uart_select/README.md @@ -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. diff --git a/examples/peripherals/uart_select/main/component.mk b/examples/peripherals/uart_select/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/peripherals/uart_select/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/uart_select/main/uart_select_example_main.c b/examples/peripherals/uart_select/main/uart_select_example_main.c new file mode 100644 index 0000000000..62bca5698c --- /dev/null +++ b/examples/peripherals/uart_select/main/uart_select_example_main.c @@ -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 +#include +#include +#include +#include +#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); +} diff --git a/examples/protocols/udp_multicast/main/udp_multicast_example_main.c b/examples/protocols/udp_multicast/main/udp_multicast_example_main.c index 830b00e07f..18678d0ea0 100644 --- a/examples/protocols/udp_multicast/main/udp_multicast_example_main.c +++ b/examples/protocols/udp_multicast/main/udp_multicast_example_main.c @@ -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; diff --git a/examples/system/select/Makefile b/examples/system/select/Makefile new file mode 100644 index 0000000000..0b30811ee2 --- /dev/null +++ b/examples/system/select/Makefile @@ -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 + diff --git a/examples/system/select/README.md b/examples/system/select/README.md new file mode 100644 index 0000000000..888d46109c --- /dev/null +++ b/examples/system/select/README.md @@ -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. diff --git a/examples/system/select/main/component.mk b/examples/system/select/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/system/select/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/system/select/main/select_example.c b/examples/system/select/main/select_example.c new file mode 100644 index 0000000000..57daf8fd83 --- /dev/null +++ b/examples/system/select/main/select_example.c @@ -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 +#include +#include +#include +#include +#include +#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); +} diff --git a/tools/unit-test-app/main/app_main.c b/tools/unit-test-app/main/app_main.c index a7a7e87542..f1c18fcbfb 100644 --- a/tools/unit-test-app/main/app_main.c +++ b/tools/unit-test-app/main/app_main.c @@ -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,