--- /dev/null
+
+// 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_
#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
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)
{
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.
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;
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;
}
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;
+}
#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 */
{ 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); }
{ 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); }
/** 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)
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
#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()
{
.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 *));
-}
--- /dev/null
+// 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__
--- /dev/null
+// 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);
+}
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
-----
#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" {
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;
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
--- /dev/null
+// 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);
+}
#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"
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) {
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;
+ }
+ }
+ }
+}
#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
// 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
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 = {
.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));
}
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
-------------
--- /dev/null
+#
+# 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
--- /dev/null
+# 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.
--- /dev/null
+#
+# Main Makefile. This is basically the same as a component makefile.
+#
--- /dev/null
+/* 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);
+}
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;
--- /dev/null
+#
+# 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
+
--- /dev/null
+# 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.
--- /dev/null
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
--- /dev/null
+/* 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);
+}
#include "freertos/task.h"
#include "unity.h"
#include "unity_config.h"
+#include "tcpip_adapter.h"
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,