]> granicus.if.org Git - esp-idf/commitdiff
Allow VFS file descriptors in select()
authorRoland Dobai <dobai.roland@gmail.com>
Thu, 3 May 2018 08:41:10 +0000 (10:41 +0200)
committerRoland Dobai <dobai.roland@gmail.com>
Fri, 18 May 2018 06:06:33 +0000 (08:06 +0200)
24 files changed:
components/driver/include/driver/uart_select.h [new file with mode: 0644]
components/driver/uart.c
components/lwip/include/lwip/lwip/sockets.h
components/lwip/include/lwip/lwip/sys.h
components/lwip/port/freertos/sys_arch.c
components/lwip/port/vfs_lwip.c
components/newlib/include/sys/select.h [new file with mode: 0644]
components/newlib/select.c [new file with mode: 0644]
components/vfs/README.rst
components/vfs/include/esp_vfs.h
components/vfs/test/test_vfs_select.c [new file with mode: 0644]
components/vfs/vfs.c
components/vfs/vfs_uart.c
docs/en/api-reference/peripherals/uart.rst
examples/peripherals/uart_select/Makefile [new file with mode: 0644]
examples/peripherals/uart_select/README.md [new file with mode: 0644]
examples/peripherals/uart_select/main/component.mk [new file with mode: 0644]
examples/peripherals/uart_select/main/uart_select_example_main.c [new file with mode: 0644]
examples/protocols/udp_multicast/main/udp_multicast_example_main.c
examples/system/select/Makefile [new file with mode: 0644]
examples/system/select/README.md [new file with mode: 0644]
examples/system/select/main/component.mk [new file with mode: 0644]
examples/system/select/main/select_example.c [new file with mode: 0644]
tools/unit-test-app/main/app_main.c

diff --git a/components/driver/include/driver/uart_select.h b/components/driver/include/driver/uart_select.h
new file mode 100644 (file)
index 0000000..24f06c1
--- /dev/null
@@ -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_
index 9cec59bf08bbbae0f5bb653d48e483136be4bb0d..e32023047e02d64575961eb023fce1145b580f46 100644 (file)
@@ -29,6 +29,7 @@
 #include "soc/uart_struct.h"
 #include "driver/uart.h"
 #include "driver/gpio.h"
+#include "driver/uart_select.h"
 
 #define XOFF (char)0x13
 #define XON (char)0x11
@@ -100,12 +101,14 @@ typedef struct {
     uint8_t tx_brk_flg;                 /*!< Flag to indicate to send a break signal in the end of the item sending procedure */
     uint8_t tx_brk_len;                 /*!< TX break signal cycle length/number */
     uint8_t tx_waiting_brk;             /*!< Flag to indicate that TX FIFO is ready to send break signal after FIFO is empty, do not push data into TX FIFO right now.*/
+    uart_select_notif_callback_t uart_select_notif_callback; /*!< Notification about select() events */
 } uart_obj_t;
 
 static uart_obj_t *p_uart_obj[UART_NUM_MAX] = {0};
 /* DRAM_ATTR is required to avoid UART array placed in flash, due to accessed from ISR */
 static DRAM_ATTR uart_dev_t* const UART[UART_NUM_MAX] = {&UART0, &UART1, &UART2};
 static portMUX_TYPE uart_spinlock[UART_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED};
+static portMUX_TYPE uart_selectlock = portMUX_INITIALIZER_UNLOCKED;
 
 esp_err_t uart_set_word_length(uart_port_t uart_num, uart_word_length_t data_bit)
 {
@@ -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;
+}
index c3cadd8730e3b031f59debe65410a078021eaa31..5978b38c4921eced6df7608d245da0ae9004daf7 100755 (executable)
@@ -38,6 +38,7 @@
 
 #if LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
 
+#include <sys/select.h>
 #include <stddef.h> /* for size_t */
 #include <string.h> /* for FD_ZERO */
 
@@ -589,8 +590,10 @@ static inline int sendto(int s,const void *dataptr,size_t size,int flags,const s
 { return lwip_sendto_r(s,dataptr,size,flags,to,tolen); }
 static inline int socket(int domain,int type,int protocol)
 { return lwip_socket(domain,type,protocol); }
+#ifndef ESP_PLATFORM
 static inline int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)
 { return lwip_select(maxfdp1,readset,writeset,exceptset,timeout); }
+#endif /* ESP_PLATFORM */
 static inline int ioctlsocket(int s,long cmd,void *argp)
 { return lwip_ioctl_r(s,cmd,argp); }
 
@@ -643,8 +646,10 @@ static inline int sendto(int s,const void *dataptr,size_t size,int flags,const s
 { return lwip_sendto(s,dataptr,size,flags,to,tolen); }
 static inline int socket(int domain,int type,int protocol)
 { return lwip_socket(domain,type,protocol); }
+#ifndef ESP_PLATFORM
 static inline int select(int maxfdp1,fd_set t*readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)
 { return lwip_select(maxfdp1,readset,writeset,exceptset,timeout); }
+#endif /* ESP_PLATFORM */
 static inline int ioctlsocket(int s,long cmd,void *argp)
 { return lwip_ioctl(s,cmd,argp); }
 
index 86d0f3b336c3829072d2d865008c79fb13029233..67729e3b73af5cd729f0c2130422fd30d11a5b45 100755 (executable)
@@ -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)
index 77eaef0a118df3523b444ee67827a5831ff3971d..2c56969ade036d8fd3aac779ee70e087f1baf2dc 100755 (executable)
@@ -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
index 2ce639e4e48f15e947dfae680cf749f71b141f0a..54d71912bb85230a9975ef04f41e6e1cc3580503 100644 (file)
 #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 (file)
index 0000000..199d481
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __ESP_SYS_SELECT_H__
+#define __ESP_SYS_SELECT_H__
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //__ESP_SYS_SELECT_H__
diff --git a/components/newlib/select.c b/components/newlib/select.c
new file mode 100644 (file)
index 0000000..dc945ea
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sys/select.h>
+#include "esp_vfs.h"
+
+int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
+{
+    return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout);
+}
index 7a25ca542d464b42523192d7d71aa115dd52d5b3..4644689a9388b9983c4b672a529c06cacb96bcc4 100644 (file)
@@ -64,6 +64,35 @@ Case 2: API functions are declared with an extra context pointer (FS driver supp
     myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
     ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));
 
+Synchronous input/output multiplexing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to use synchronous input/output multiplexing by :cpp:func:`select`
+then you need to register the VFS with :cpp:func:`start_select` and
+:cpp:func:`end_select` functions similarly to the following example:
+
+.. highlight:: c
+
+::
+
+    // In definition of esp_vfs_t:
+        .start_select = &uart_start_select,
+        .end_select = &uart_end_select,
+    // ... other members initialized
+
+:cpp:func:`start_select` is called for setting up the environment for
+detection of read/write/error conditions on file descriptors belonging to the
+given VFS. :cpp:func:`end_select` is called to stop/deinitialize/free the
+environment which was setup by :cpp:func:`start_select`. Please refer to the
+reference implementation for the UART peripheral in
+:component_file:`vfs/vfs_uart.c` and most particularly to functions
+:cpp:func:`esp_vfs_dev_uart_register`, :cpp:func:`uart_start_select` and
+:cpp:func:`uart_end_select`.
+
+Examples demonstrating the use of :cpp:func:`select` with VFS file descriptors
+are the :example:`peripherals/uart_select` and the :example:`system/select`
+examples.
+
 Paths
 -----
 
index 202be24859d0c410e96b491547e3f6b52df46e41..d063dba0ac60ba973c95d13764aa4958e574b2af 100644 (file)
@@ -25,7 +25,9 @@
 #include <sys/types.h>
 #include <sys/reent.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <dirent.h>
+#include <string.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -167,6 +169,16 @@ typedef struct
         int (*access_p)(void* ctx, const char *path, int amode);
         int (*access)(const char *path, int amode);
     };
+    /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */
+    esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds);
+    /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */
+    int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+    /** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */
+    void (*stop_socket_select)();
+    /** stop_socket_select which can be called from ISR; set only for the socket driver */
+    void (*stop_socket_select_isr)(BaseType_t *woken);
+    /** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */
+    void (*end_select)();
 } esp_vfs_t;
 
 
@@ -235,6 +247,50 @@ int esp_vfs_unlink(struct _reent *r, const char *path);
 int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
 /**@}*/
 
+/**
+ * @brief Synchronous I/O multiplexing which implements the functionality of POSIX select() for VFS
+ * @param nfds      Specifies the range of descriptors which should be checked.
+ *                  The first nfds descriptors will be checked in each set.
+ * @param readfds   If not NULL, then points to a descriptor set that on input
+ *                  specifies which descriptors should be checked for being
+ *                  ready to read, and on output indicates which descriptors
+ *                  are ready to read.
+ * @param writefds  If not NULL, then points to a descriptor set that on input
+ *                  specifies which descriptors should be checked for being
+ *                  ready to write, and on output indicates which descriptors
+ *                  are ready to write.
+ * @param errorfds  If not NULL, then points to a descriptor set that on input
+ *                  specifies which descriptors should be checked for error
+ *                  conditions, and on output indicates which descriptors
+ *                  have error conditions.
+ * @param timeout   If not NULL, then points to timeval structure which
+ *                  specifies the time period after which the functions should
+ *                  time-out and return. If it is NULL, then the function will
+ *                  not time-out.
+ *
+ * @return      The number of descriptors set in the descriptor sets, or -1
+ *              when an error (specified by errno) have occurred.
+ */
+int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ */
+void esp_vfs_select_triggered();
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition (ISR version)
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ *
+ * @param woken is set to pdTRUE if the function wakes up a task with higher priority
+ */
+void esp_vfs_select_triggered_isr(BaseType_t *woken);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/components/vfs/test/test_vfs_select.c b/components/vfs/test/test_vfs_select.c
new file mode 100644 (file)
index 0000000..5febf39
--- /dev/null
@@ -0,0 +1,284 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/fcntl.h>
+#include <sys/param.h>
+#include "unity.h"
+#include "soc/uart_struct.h"
+#include "freertos/FreeRTOS.h"
+#include "driver/uart.h"
+#include "esp_vfs.h"
+#include "esp_vfs_dev.h"
+#include "lwip/sockets.h"
+#include "lwip/netdb.h"
+
+typedef struct {
+    int fd;
+    int delay_ms;
+    xSemaphoreHandle sem;
+} test_task_param_t;
+
+static const char message[] = "Hello world!";
+
+static int socket_init()
+{
+    const struct addrinfo hints = {
+        .ai_family = AF_INET,
+        .ai_socktype = SOCK_DGRAM,
+    };
+    struct addrinfo *res;
+    int err;
+    struct sockaddr_in saddr = { 0 };
+    int socket_fd = -1;
+
+    err = getaddrinfo("localhost", "80", &hints, &res);
+    TEST_ASSERT_EQUAL(err, 0);
+    TEST_ASSERT_NOT_NULL(res);
+
+    socket_fd = socket(res->ai_family, res->ai_socktype, 0);
+    TEST_ASSERT(socket_fd >= 0);
+
+    saddr.sin_family = PF_INET;
+    saddr.sin_port = htons(80);
+    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    err = bind(socket_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
+    TEST_ASSERT(err >= 0);
+
+    err = connect(socket_fd, res->ai_addr, res->ai_addrlen);
+    TEST_ASSERT_EQUAL_MESSAGE(err, 0, "Socket connection failed");
+
+    freeaddrinfo(res);
+
+    return socket_fd;
+}
+
+static void uart1_init()
+{
+    uart_config_t uart_config = {
+        .baud_rate = 115200,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
+    };
+    uart_param_config(UART_NUM_1, &uart_config);
+    uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0);
+}
+
+static void send_task(void *param)
+{
+    const test_task_param_t *test_task_param = param;
+    vTaskDelay(test_task_param->delay_ms / portTICK_PERIOD_MS);
+    write(test_task_param->fd, message, sizeof(message));
+    if (test_task_param->sem) {
+        xSemaphoreGive(test_task_param->sem);
+    }
+    vTaskDelete(NULL);
+}
+
+static inline void start_task(const test_task_param_t *test_task_param)
+{
+    xTaskCreate(send_task, "send_task", 4*1024, (void *) test_task_param, 5, NULL);
+}
+
+static void init(int *uart_fd, int *socket_fd)
+{
+    uart1_init();
+    UART1.conf0.loopback = 1;
+
+    *uart_fd = open("/dev/uart/1", O_RDWR);
+    TEST_ASSERT_NOT_EQUAL_MESSAGE(*uart_fd, -1, "Cannot open UART");
+
+    esp_vfs_dev_uart_use_driver(1);
+
+    *socket_fd = socket_init();
+}
+
+static void deinit(int uart_fd, int socket_fd)
+{
+    esp_vfs_dev_uart_use_nonblocking(1);
+    close(uart_fd);
+    UART1.conf0.loopback = 0;
+    uart_driver_delete(UART_NUM_1);
+
+    close(socket_fd);
+}
+
+TEST_CASE("UART can do select()", "[vfs]")
+{
+    int uart_fd;
+    int socket_fd;
+    struct timeval tv = {
+        .tv_sec = 0,
+        .tv_usec = 100000,
+    };
+    char recv_message[sizeof(message)];
+
+    init(&uart_fd, &socket_fd);
+
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(uart_fd, &rfds);
+    //without socket in rfds it will not use the same signalization
+
+    const test_task_param_t test_task_param = {
+        .fd = uart_fd,
+        .delay_ms = 50,
+        .sem = xSemaphoreCreateBinary(),
+    };
+    TEST_ASSERT_NOT_NULL(test_task_param.sem);
+    start_task(&test_task_param);
+
+    int s = select(uart_fd + 1, &rfds, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 1);
+    TEST_ASSERT(FD_ISSET(uart_fd, &rfds));
+    TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
+
+    int read_bytes = read(uart_fd, recv_message, sizeof(message));
+    TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
+    TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
+
+    TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
+
+    FD_ZERO(&rfds);
+    FD_SET(uart_fd, &rfds);
+    FD_SET(socket_fd, &rfds);
+
+    start_task(&test_task_param);
+
+    s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 1);
+    TEST_ASSERT(FD_ISSET(uart_fd, &rfds));
+    TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
+
+    read_bytes = read(uart_fd, recv_message, sizeof(message));
+    TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
+    TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
+
+    TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
+    vSemaphoreDelete(test_task_param.sem);
+
+    deinit(uart_fd, socket_fd);
+}
+
+TEST_CASE("socket can do select()", "[vfs]")
+{
+    int uart_fd;
+    int socket_fd;
+    struct timeval tv = {
+        .tv_sec = 0,
+        .tv_usec = 100000,
+    };
+    char recv_message[sizeof(message)];
+
+    init(&uart_fd, &socket_fd);
+
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(uart_fd, &rfds);
+    FD_SET(socket_fd, &rfds);
+
+    const test_task_param_t test_task_param = {
+        .fd = socket_fd,
+        .delay_ms = 50,
+        .sem = xSemaphoreCreateBinary(),
+    };
+    TEST_ASSERT_NOT_NULL(test_task_param.sem);
+    start_task(&test_task_param);
+
+    const int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 1);
+    TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
+    TEST_ASSERT(FD_ISSET(socket_fd, &rfds));
+
+    int read_bytes = read(socket_fd, recv_message, sizeof(message));
+    TEST_ASSERT_EQUAL(read_bytes, sizeof(message));
+    TEST_ASSERT_EQUAL_MEMORY(message, recv_message, sizeof(message));
+
+    TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
+    vSemaphoreDelete(test_task_param.sem);
+
+    deinit(uart_fd, socket_fd);
+}
+
+TEST_CASE("select() timeout", "[vfs]")
+{
+    int uart_fd;
+    int socket_fd;
+    struct timeval tv = {
+        .tv_sec = 0,
+        .tv_usec = 100000,
+    };
+
+    init(&uart_fd, &socket_fd);
+
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(uart_fd, &rfds);
+    FD_SET(socket_fd, &rfds);
+
+    int s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 0);
+    TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
+    TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
+
+    FD_ZERO(&rfds);
+
+    s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 0);
+    TEST_ASSERT_UNLESS(FD_ISSET(uart_fd, &rfds));
+    TEST_ASSERT_UNLESS(FD_ISSET(socket_fd, &rfds));
+
+    deinit(uart_fd, socket_fd);
+}
+
+static void select_task(void *param)
+{
+    const test_task_param_t *test_task_param = param;
+    struct timeval tv = {
+        .tv_sec = 0,
+        .tv_usec = 100000,
+    };
+
+    int s = select(1, NULL, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, 0); //timeout
+
+    if (test_task_param->sem) {
+        xSemaphoreGive(test_task_param->sem);
+    }
+    vTaskDelete(NULL);
+}
+
+TEST_CASE("concurent select() fails", "[vfs]")
+{
+    struct timeval tv = {
+        .tv_sec = 0,
+        .tv_usec = 100000,//irrelevant
+    };
+    const test_task_param_t test_task_param = {
+        .sem = xSemaphoreCreateBinary(),
+    };
+    TEST_ASSERT_NOT_NULL(test_task_param.sem);
+    xTaskCreate(select_task, "select_task", 4*1024, (void *) &test_task_param, 5, NULL);
+    vTaskDelay(10 / portTICK_PERIOD_MS); //make sure the task has started and waits in select()
+
+    int s = select(1, NULL, NULL, NULL, &tv);
+    TEST_ASSERT_EQUAL(s, -1); //this select should fail because the other one is "waiting"
+    TEST_ASSERT_EQUAL(errno, EINTR);
+
+    TEST_ASSERT_EQUAL(xSemaphoreTake(test_task_param.sem, 1000 / portTICK_PERIOD_MS), pdTRUE);
+    vSemaphoreDelete(test_task_param.sem);
+}
index d7ca283ec34344b31d8abdad0c55af422c48a074..7ea36b0391ace8995257711c569b43a0ac7143d7 100644 (file)
 #include <sys/ioctl.h>
 #include <sys/unistd.h>
 #include <sys/lock.h>
+#include <sys/param.h>
 #include <dirent.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
 #include "esp_vfs.h"
 #include "esp_log.h"
 
@@ -49,12 +52,28 @@ typedef struct vfs_entry_ {
     int offset;             // index of this structure in s_vfs array
 } vfs_entry_t;
 
+typedef struct {
+    bool isset; // none or at least one bit is set in the following 3 fd sets
+    fd_set readfds;
+    fd_set writefds;
+    fd_set errorfds;
+} fds_triple_t;
+
 static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 };
 static size_t s_vfs_count = 0;
 
 static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED };
 static _lock_t s_fd_table_lock;
 
+/* Semaphore used for waiting select events from other VFS drivers when socket
+ * select is not used (not registered or socket FDs are not observed by the
+ * given call of select)
+ */
+static SemaphoreHandle_t s_select_sem = NULL;
+
+/* Lock ensuring that select is called from only one task at the time */
+static _lock_t s_one_select_lock;
+
 static esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index)
 {
     if (len != LEN_PATH_PREFIX_IGNORED) {
@@ -637,3 +656,214 @@ int access(const char *path, int amode)
     CHECK_AND_CALL(ret, r, vfs, access, path_within_vfs, amode);
     return ret;
 }
+
+static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple)
+{
+    for (int i = 0; i < end_index; ++i) {
+        const vfs_entry_t *vfs = get_vfs_for_index(i);
+        const fds_triple_t *item = &vfs_fds_triple[i];
+        if (vfs && vfs->vfs.end_select && item->isset) {
+            vfs->vfs.end_select();
+        }
+    }
+}
+
+static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_set *readfds, fd_set *writefds, fd_set *errorfds)
+{
+    int ret = 0;
+
+    for (int i = 0; i < size; ++i) {
+        const fds_triple_t *item = &vfs_fds_triple[i];
+        if (item->isset) {
+            for (int fd = 0; fd < MAX_FDS; ++fd) {
+                const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
+                if (readfds && FD_ISSET(local_fd, &item->readfds)) {
+                    FD_SET(fd, readfds);
+                    ++ret;
+                }
+                if (writefds && FD_ISSET(local_fd, &item->writefds)) {
+                    FD_SET(fd, writefds);
+                    ++ret;
+                }
+                if (errorfds && FD_ISSET(local_fd, &item->errorfds)) {
+                    FD_SET(fd, errorfds);
+                    ++ret;
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
+{
+    int ret = 0;
+    struct _reent* r = __getreent();
+
+    if (nfds > MAX_FDS || nfds < 0) {
+        __errno_r(r) = EINVAL;
+        return -1;
+    }
+
+    if (_lock_try_acquire(&s_one_select_lock)) {
+        __errno_r(r) = EINTR;
+        return -1;
+    }
+
+    fds_triple_t *vfs_fds_triple;
+    if ((vfs_fds_triple = calloc(s_vfs_count, sizeof(fds_triple_t))) == NULL) {
+        __errno_r(r) = ENOMEM;
+        _lock_release(&s_one_select_lock);
+        return -1;
+    }
+
+    int (*socket_select)(int, fd_set *, fd_set *, fd_set *, struct timeval *) = NULL;
+    for (int fd = 0; fd < nfds; ++fd) {
+        _lock_acquire(&s_fd_table_lock);
+        const bool is_socket_fd = s_fd_table[fd].permanent;
+        const int vfs_index = s_fd_table[fd].vfs_index;
+        const int local_fd = s_fd_table[fd].local_fd;
+        _lock_release(&s_fd_table_lock);
+
+        if (vfs_index < 0) {
+            continue;
+        }
+
+        if (!socket_select && is_socket_fd) {
+            // no socket_select found yet and the fd is for a socket so take a look
+            if ((readfds && FD_ISSET(fd, readfds)) ||
+                    (writefds && FD_ISSET(fd, writefds)) ||
+                    (errorfds && FD_ISSET(fd, errorfds))) {
+                const vfs_entry_t *vfs = s_vfs[vfs_index];
+                socket_select = vfs->vfs.socket_select;
+            }
+            continue;
+        }
+
+        fds_triple_t *item = &vfs_fds_triple[vfs_index]; // FD sets for VFS which belongs to fd
+        if (readfds && FD_ISSET(fd, readfds)) {
+            item->isset = true;
+            FD_SET(local_fd, &item->readfds);
+            FD_CLR(fd, readfds);
+        }
+        if (writefds && FD_ISSET(fd, writefds)) {
+            item->isset = true;
+            FD_SET(local_fd, &item->writefds);
+            FD_CLR(fd, writefds);
+        }
+        if (errorfds && FD_ISSET(fd, errorfds)) {
+            item->isset = true;
+            FD_SET(local_fd, &item->errorfds);
+            FD_CLR(fd, errorfds);
+        }
+    }
+
+    // all non-socket VFSs have their FD sets in vfs_fds_triple
+    // the global readfds, writefds and errorfds contain only socket FDs (if
+    // there any)
+
+    if (!socket_select) {
+        // There is no socket VFS registered or select() wasn't called for
+        // any socket. Therefore, we will use our own signalization.
+        if ((s_select_sem = xSemaphoreCreateBinary()) == NULL) {
+            free(vfs_fds_triple);
+            __errno_r(r) = ENOMEM;
+            _lock_release(&s_one_select_lock);
+            return -1;
+        }
+    }
+
+    for (int i = 0; i < s_vfs_count; ++i) {
+        const vfs_entry_t *vfs = get_vfs_for_index(i);
+        fds_triple_t *item = &vfs_fds_triple[i];
+
+        if (vfs && vfs->vfs.start_select && item->isset) {
+            // call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds
+            // note: it can point to socket VFS but item->isset will be false for that
+            esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds);
+
+            if (err != ESP_OK) {
+                call_end_selects(i, vfs_fds_triple);
+                (void) set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds);
+                if (s_select_sem) {
+                    vSemaphoreDelete(s_select_sem);
+                    s_select_sem = NULL;
+                }
+                free(vfs_fds_triple);
+                __errno_r(r) = ENOMEM;
+                _lock_release(&s_one_select_lock);
+                return -1;
+            }
+        }
+    }
+
+    if (socket_select) {
+        ret = socket_select(nfds, readfds, writefds, errorfds, timeout);
+    } else {
+        if (readfds) {
+            FD_ZERO(readfds);
+        }
+        if (writefds) {
+            FD_ZERO(writefds);
+        }
+        if (errorfds) {
+            FD_ZERO(errorfds);
+        }
+
+        TickType_t ticks_to_wait = portMAX_DELAY;
+        if (timeout) {
+            uint32_t timeout_ms = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
+            ticks_to_wait = timeout_ms / portTICK_PERIOD_MS;
+        }
+        xSemaphoreTake(s_select_sem, ticks_to_wait);
+    }
+
+    call_end_selects(s_vfs_count, vfs_fds_triple); // for VFSs for start_select was called before
+    if (ret >= 0) {
+        ret += set_global_fd_sets(vfs_fds_triple, s_vfs_count, readfds, writefds, errorfds);
+    }
+    if (s_select_sem) {
+        vSemaphoreDelete(s_select_sem);
+        s_select_sem = NULL;
+    }
+    free(vfs_fds_triple);
+    _lock_release(&s_one_select_lock);
+    return ret;
+}
+
+void esp_vfs_select_triggered()
+{
+    if (s_select_sem) {
+        xSemaphoreGive(s_select_sem);
+    } else {
+        // Another way would be to go through s_fd_table and find the VFS
+        // which has a permanent FD. But in order to avoid to lock
+        // s_fd_table_lock we go through the VFS table.
+        for (int i = 0; i < s_vfs_count; ++i) {
+            const vfs_entry_t *vfs = s_vfs[i];
+            if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) {
+                vfs->vfs.stop_socket_select();
+                break;
+            }
+        }
+    }
+}
+
+void esp_vfs_select_triggered_isr(BaseType_t *woken)
+{
+    if (s_select_sem) {
+        xSemaphoreGiveFromISR(s_select_sem, woken);
+    } else {
+        // Another way would be to go through s_fd_table and find the VFS
+        // which has a permanent FD. But in order to avoid to lock
+        // s_fd_table_lock we go through the VFS table.
+        for (int i = 0; i < s_vfs_count; ++i) {
+            const vfs_entry_t *vfs = s_vfs[i];
+            if (vfs != NULL && vfs->vfs.stop_socket_select_isr != NULL) {
+                vfs->vfs.stop_socket_select_isr(woken);
+                break;
+            }
+        }
+    }
+}
index 83e89acc34cd12f1ddcfb8ff0f4b6d95302a6bdd..c1c12066cbaebbac7b28f6fbd65cc14f6598b815 100644 (file)
 #include <sys/errno.h>
 #include <sys/lock.h>
 #include <sys/fcntl.h>
+#include <sys/param.h>
 #include "esp_vfs.h"
 #include "esp_vfs_dev.h"
 #include "esp_attr.h"
 #include "soc/uart_struct.h"
 #include "driver/uart.h"
 #include "sdkconfig.h"
+#include "driver/uart_select.h"
 
 // TODO: make the number of UARTs chip dependent
 #define UART_NUM 3
@@ -56,6 +58,13 @@ static int s_peek_char[UART_NUM] = { NONE, NONE, NONE };
 // driver is used.
 static bool s_non_blocking[UART_NUM];
 
+static fd_set *_readfds = NULL;
+static fd_set *_writefds = NULL;
+static fd_set *_errorfds = NULL;
+static fd_set *_readfds_orig = NULL;
+static fd_set *_writefds_orig = NULL;
+static fd_set *_errorfds_orig = NULL;
+
 // Newline conversion mode when transmitting
 static esp_line_endings_t s_tx_mode =
 #if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
@@ -288,6 +297,108 @@ static int uart_access(const char *path, int amode)
     return ret;
 }
 
+static void select_notif_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken)
+{
+    switch (uart_select_notif) {
+        case UART_SELECT_READ_NOTIF:
+            if (FD_ISSET(uart_num, _readfds_orig)) {
+                FD_SET(uart_num, _readfds);
+                esp_vfs_select_triggered_isr(task_woken);
+            }
+            break;
+        case UART_SELECT_WRITE_NOTIF:
+            if (FD_ISSET(uart_num, _writefds_orig)) {
+                FD_SET(uart_num, _writefds);
+                esp_vfs_select_triggered_isr(task_woken);
+            }
+            break;
+        case UART_SELECT_ERROR_NOTIF:
+            if (FD_ISSET(uart_num, _errorfds_orig)) {
+                FD_SET(uart_num, _errorfds);
+                esp_vfs_select_triggered_isr(task_woken);
+            }
+            break;
+    }
+}
+
+static esp_err_t uart_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds)
+{
+    const int max_fds = MIN(nfds, UART_NUM);
+
+    taskENTER_CRITICAL(uart_get_selectlock());
+
+    if (_readfds || _writefds || _errorfds || _readfds_orig || _writefds_orig || _errorfds_orig) {
+        taskEXIT_CRITICAL(uart_get_selectlock());
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    if ((_readfds_orig = malloc(sizeof(fd_set))) == NULL) {
+        taskEXIT_CRITICAL(uart_get_selectlock());
+        return ESP_ERR_NO_MEM;
+    }
+
+    if ((_writefds_orig = malloc(sizeof(fd_set))) == NULL) {
+        taskEXIT_CRITICAL(uart_get_selectlock());
+        return ESP_ERR_NO_MEM;
+    }
+
+    if ((_errorfds_orig = malloc(sizeof(fd_set))) == NULL) {
+        taskEXIT_CRITICAL(uart_get_selectlock());
+        return ESP_ERR_NO_MEM;
+    }
+
+    //uart_set_select_notif_callback set the callbacks in UART ISR
+    for (int i = 0; i < max_fds; ++i) {
+        if (FD_ISSET(i, readfds) || FD_ISSET(i, writefds) || FD_ISSET(i, exceptfds)) {
+            uart_set_select_notif_callback(i, select_notif_callback);
+        }
+    }
+
+    _readfds = readfds;
+    _writefds = writefds;
+    _errorfds = exceptfds;
+
+    *_readfds_orig = *readfds;
+    *_writefds_orig = *writefds;
+    *_errorfds_orig = *exceptfds;
+
+    FD_ZERO(readfds);
+    FD_ZERO(writefds);
+    FD_ZERO(exceptfds);
+
+    taskEXIT_CRITICAL(uart_get_selectlock());
+
+    return ESP_OK;
+}
+
+static void uart_end_select()
+{
+    taskENTER_CRITICAL(uart_get_selectlock());
+    for (int i = 0; i < UART_NUM; ++i) {
+        uart_set_select_notif_callback(i, NULL);
+    }
+
+    _readfds = NULL;
+    _writefds = NULL;
+    _errorfds = NULL;
+
+    if (_readfds_orig) {
+        free(_readfds_orig);
+        _readfds_orig = NULL;
+    }
+
+    if (_writefds_orig) {
+        free(_writefds_orig);
+        _writefds_orig = NULL;
+    }
+
+    if (_errorfds_orig) {
+        free(_errorfds_orig);
+        _errorfds_orig = NULL;
+    }
+    taskEXIT_CRITICAL(uart_get_selectlock());
+}
+
 void esp_vfs_dev_uart_register()
 {
     esp_vfs_t vfs = {
@@ -299,6 +410,8 @@ void esp_vfs_dev_uart_register()
         .read = &uart_read,
         .fcntl = &uart_fcntl,
         .access = &uart_access,
+        .start_select = &uart_start_select,
+        .end_select = &uart_end_select,
     };
     ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL));
 }
index d7c82c37edfc65ec8bbb5b7c19dfb165517ef09d..ea2e9615b79c8545875a383f475478f6983a9824 100644 (file)
@@ -142,6 +142,8 @@ Demonstration of how to report various communication events and how to use pater
 
 Transmitting and receiveing with the same UART in two separate FreeRTOS tasks: :example:`peripherals/uart_async_rxtxtasks`.
 
+Using synchronous I/O multiplexing for UART file descriptors: :example:`peripherals/uart_select`.
+
 
 API Reference
 -------------
diff --git a/examples/peripherals/uart_select/Makefile b/examples/peripherals/uart_select/Makefile
new file mode 100644 (file)
index 0000000..010458f
--- /dev/null
@@ -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 (file)
index 0000000..966e986
--- /dev/null
@@ -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 (file)
index 0000000..44bd2b5
--- /dev/null
@@ -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 (file)
index 0000000..62bca56
--- /dev/null
@@ -0,0 +1,90 @@
+/* UART Select Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <stdio.h>
+#include <sys/fcntl.h>
+#include <sys/errno.h>
+#include <sys/unistd.h>
+#include <sys/select.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+
+static const char* TAG = "uart_select_example";
+
+static void uart_select_task()
+{
+    uart_config_t uart_config = {
+        .baud_rate = 115200,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
+    };
+    uart_param_config(UART_NUM_0, &uart_config);
+    uart_driver_install(UART_NUM_0, 2*1024, 0, 0, NULL, 0);
+
+    while (1) {
+        int fd;
+
+        if ((fd = open("/dev/uart/0", O_RDWR)) == -1) {
+            ESP_LOGE(TAG, "Cannot open UART");
+            vTaskDelay(5000 / portTICK_PERIOD_MS);
+            continue;
+        }
+
+        // We have a driver now installed so set up the read/write functions to use driver also.
+        esp_vfs_dev_uart_use_driver(0);
+
+        while (1) {
+            int s;
+            fd_set rfds;
+            struct timeval tv = {
+                .tv_sec = 5,
+                .tv_usec = 0,
+            };
+
+            FD_ZERO(&rfds);
+            FD_SET(fd, &rfds);
+
+            s = select(fd + 1, &rfds, NULL, NULL, &tv);
+
+            if (s < 0) {
+                ESP_LOGE(TAG, "Select failed: errno %d", errno);
+                break;
+            } else if (s == 0) {
+                ESP_LOGI(TAG, "Timeout has been reached and nothing has been received");
+            } else {
+                if (FD_ISSET(fd, &rfds)) {
+                    char buf;
+                    if (read(fd, &buf, 1) > 0) {
+                            ESP_LOGI(TAG, "Received: %c", buf);
+                    } else {
+                        ESP_LOGE(TAG, "UART read error");
+                        break;
+                    }
+                } else {
+                    ESP_LOGE(TAG, "No FD has been set in select()");
+                    break;
+                }
+            }
+        }
+
+        close(fd);
+    }
+
+    vTaskDelete(NULL);
+}
+
+void app_main()
+{
+    xTaskCreate(uart_select_task, "uart_select_task", 4*1024, NULL, 5, NULL);
+}
index 830b00e07fd7d73a8d0c60f55344c2f6d03b1f55..18678d0ea04b82a8778517ee4d71fcec35f98416 100644 (file)
@@ -420,7 +420,7 @@ static void mcast_example_task(void *pvParameters)
             FD_ZERO(&rfds);
             FD_SET(sock, &rfds);
 
-            int s = lwip_select(sock + 1, &rfds, NULL, NULL, &tv);
+            int s = select(sock + 1, &rfds, NULL, NULL, &tv);
             if (s < 0) {
                 ESP_LOGE(TAG, "Select failed: errno %d", errno);
                 err = -1;
diff --git a/examples/system/select/Makefile b/examples/system/select/Makefile
new file mode 100644 (file)
index 0000000..0b30811
--- /dev/null
@@ -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 (file)
index 0000000..888d461
--- /dev/null
@@ -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 (file)
index 0000000..0b9d758
--- /dev/null
@@ -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 (file)
index 0000000..57daf8f
--- /dev/null
@@ -0,0 +1,207 @@
+/* Select Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <stdio.h>
+#include <sys/fcntl.h>
+#include <sys/errno.h>
+#include <sys/unistd.h>
+#include <sys/param.h>
+#include <sys/select.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+#include "tcpip_adapter.h"
+#include "lwip/sockets.h"
+#include "lwip/netdb.h"
+
+static const char* TAG = "uart_select_example";
+
+static int uart_fd = -1;
+static int socket_fd = -1;
+
+static void socket_deinit()
+{
+    close(socket_fd);
+    socket_fd = -1;
+}
+
+static void socket_init()
+{
+    const struct addrinfo hints = {
+        .ai_family = AF_INET,
+        .ai_socktype = SOCK_DGRAM,
+    };
+    struct addrinfo *res;
+    int err;
+    struct sockaddr_in saddr = { 0 };
+
+    tcpip_adapter_init();
+
+    err = getaddrinfo("localhost", "80", &hints, &res);
+
+    if (err != 0 || res == NULL) {
+        ESP_LOGE(TAG, "DNS lookup failed: %d", errno);
+        return;
+    }
+
+    socket_fd = socket(res->ai_family, res->ai_socktype, 0);
+
+    if (socket_fd < 0) {
+        ESP_LOGE(TAG, "Failed to allocate socket.");
+        freeaddrinfo(res);
+        return;
+    }
+
+    saddr.sin_family = PF_INET;
+    saddr.sin_port = htons(80);
+    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    err = bind(socket_fd, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
+    if (err < 0) {
+        ESP_LOGE(TAG, "Failed to bind socket");
+        freeaddrinfo(res);
+        socket_deinit();
+        return;
+    }
+
+    if (connect(socket_fd, res->ai_addr, res->ai_addrlen) != 0) {
+        ESP_LOGE(TAG, "Socket connection failed: %d", errno);
+        freeaddrinfo(res);
+        socket_deinit();
+        return;
+    }
+
+    freeaddrinfo(res);
+}
+
+static void uart1_deinit()
+{
+    close(uart_fd);
+    uart_fd = -1;
+    uart_driver_delete(UART_NUM_1);
+    UART1.conf0.loopback = 0;
+}
+
+static void uart1_init()
+{
+    uart_config_t uart_config = {
+        .baud_rate = 115200,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
+    };
+    uart_param_config(UART_NUM_1, &uart_config);
+    uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0);
+    UART1.conf0.loopback = 1;
+
+    if ((uart_fd = open("/dev/uart/1", O_RDWR | O_NONBLOCK)) == -1) {
+        ESP_LOGE(TAG, "Cannot open UART1");
+        uart1_deinit();
+    }
+
+    esp_vfs_dev_uart_use_driver(1);
+}
+
+static void uart1_write_task(void *param)
+{
+    char buf[20];
+
+    uart1_init();
+
+    for (uint8_t i = 1;; ++i) {
+        vTaskDelay(4000 / portTICK_PERIOD_MS);
+
+        snprintf(buf, sizeof(buf), "UART message U%d", i);
+        int write_bytes = write(uart_fd, buf, strlen(buf));
+        if (write_bytes < 0) {
+            ESP_LOGE(TAG, "UART1 write error");
+        } else {
+            ESP_LOGI(TAG, "%d bytes were sent to UART1: %s", write_bytes, buf);
+        }
+    }
+
+    uart1_deinit(uart_fd);
+    vTaskDelete(NULL);
+}
+
+static void socket_write_task(void *param)
+{
+    char buf[20];
+
+    socket_init();
+
+    for (uint8_t i = 1;; ++i) {
+        vTaskDelay(3000 / portTICK_PERIOD_MS);
+
+        snprintf(buf, sizeof(buf), "Socket message S%d", i);
+        int write_bytes = write(socket_fd, buf, strlen(buf));
+        if (write_bytes < 0) {
+            ESP_LOGE(TAG, "Socket write error");
+        } else {
+            ESP_LOGI(TAG, "%d bytes were written to socket: %s", write_bytes, buf);
+        }
+    }
+
+    socket_deinit();
+    vTaskDelete(NULL);
+}
+
+static void check_and_print(int fd, const fd_set *rfds, const char *src_msg)
+{
+    char buf[100];
+    int read_bytes;
+
+    if (FD_ISSET(fd, rfds)) {
+        if ((read_bytes = read(fd, buf, sizeof(buf)-1)) > 0) {
+            buf[read_bytes] = '\0';
+            ESP_LOGI(TAG, "%d bytes were received through %s: %s", read_bytes, src_msg, buf);
+        } else {
+            ESP_LOGE(TAG, "%s read error", src_msg);
+        }
+    }
+}
+
+static void select_task(void *param)
+{
+    while (1) {
+        int s;
+        fd_set rfds;
+        struct timeval tv = {
+            .tv_sec = 1,
+            .tv_usec = 0,
+        };
+
+        FD_ZERO(&rfds);
+        FD_SET(uart_fd, &rfds);
+        FD_SET(socket_fd, &rfds);
+
+        s = select(MAX(uart_fd, socket_fd) + 1, &rfds, NULL, NULL, &tv);
+
+        if (s < 0) {
+            ESP_LOGE(TAG, "Select failed: errno %d", errno);
+        } else if (s == 0) {
+            ESP_LOGI(TAG, "Timeout has been reached and nothing has been received");
+        } else {
+            check_and_print(socket_fd, &rfds, "socket");
+            check_and_print(uart_fd, &rfds, "UART1");
+        }
+    }
+
+    vTaskDelete(NULL);
+}
+
+void app_main()
+{
+    xTaskCreate(uart1_write_task, "uart1_write_task", 4*1024, NULL, 5, NULL);
+    xTaskCreate(socket_write_task, "socket_write_task", 4*1024, NULL, 5, NULL);
+    vTaskDelay(1000 / portTICK_PERIOD_MS);
+    xTaskCreate(select_task, "select_task", 4*1024, NULL, 5, NULL);
+}
index a7a7e8754216aff3e1eb8cbe9442997f7179e8db..f1c18fcbfb0c1979338c20418f2e9ac63c183c0b 100644 (file)
@@ -3,6 +3,7 @@
 #include "freertos/task.h"
 #include "unity.h"
 #include "unity_config.h"
+#include "tcpip_adapter.h"
 
 void unityTask(void *pvParameters)
 {
@@ -12,6 +13,10 @@ void unityTask(void *pvParameters)
 
 void app_main()
 {
+    // TCP/IP adapter is initialized here because it leaks memory so the
+    // initialization in test cases would make the test fail because of leak.
+    tcpip_adapter_init();
+
     // Note: if unpinning this task, change the way run times are calculated in
     // unity_platform
     xTaskCreatePinnedToCore(unityTask, "unityTask", 8192, NULL,