Parcourir la source

Merge branch 'feature/add-eventfd' into 'master'

vfs: add eventfd support

See merge request espressif/esp-idf!12573
Ivan Grokhotkov il y a 4 ans
Parent
commit
fa72ef6bd8

+ 1 - 1
.gitlab/ci/target-test.yml

@@ -411,7 +411,7 @@ UT_001:
 
 UT_002:
   extends: .unit_test_esp32_template
-  parallel: 13
+  parallel: 15
   tags:
     - ESP32_IDF
     - UT_T1_1

+ 1 - 0
components/vfs/CMakeLists.txt

@@ -1,4 +1,5 @@
 idf_component_register(SRCS "vfs.c"
+                            "vfs_eventfd.c"
                             "vfs_uart.c"
                             "vfs_semihost.c"
                     INCLUDE_DIRS include)

+ 11 - 0
components/vfs/README.rst

@@ -235,3 +235,14 @@ Such a design has the following consequences:
 - It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g., by doing ``stdin = fopen("/dev/uart/1", "r")``.
 - Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object, which will affect all other tasks.
 - To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task.
+
+Event fds
+-------------------------------------------
+
+``eventfd()`` call is a powerful tool to notify a ``select()`` based loop of custom events. The ``eventfd()`` implementation in ESP-IDF is generally the same as described in ``man(2) eventfd`` except for:
+
+- ``esp_vfs_eventfd_register()`` has to be called before calling ``eventfd()``
+- Options ``EFD_CLOEXEC``, ``EFD_NONBLOCK`` and ``EFD_SEMAPHORE`` are not supported in flags.
+- Option ``EFD_SUPPORT_ISR`` has been added in flags. This flag is required to read and the write the eventfd in an interrupt handler.
+
+Note that creating an eventfd with ``EFD_SUPPORT_ISR`` will cause interrupts to be temporarily disabled when reading, writing the file and during the beginning and the ending of the ``select()`` when this file is set.

+ 24 - 0
components/vfs/include/esp_vfs.h

@@ -325,6 +325,15 @@ esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t
  */
 esp_err_t esp_vfs_unregister(const char* base_path);
 
+/**
+ * Unregister a virtual filesystem with the given index
+ *
+ * @param vfs_id  The VFS ID returned by esp_vfs_register_with_id
+ * @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for the given index
+ *         hasn't been registered
+ */
+esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id);
+
 /**
  * Special function for registering another file descriptor for a VFS registered
  * by esp_vfs_register_with_id.
@@ -338,6 +347,21 @@ esp_err_t esp_vfs_unregister(const char* base_path);
  */
 esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd);
 
+/**
+ * Special function for registering another file descriptor with given local_fd
+ * for a VFS registered by esp_vfs_register_with_id.
+ *
+ * @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
+ * @param local_fd The fd in the local vfs. Passing -1 will set the local fd as the (*fd) value.
+ * @param permanent Whether the fd should be treated as permannet (not removed after close())
+ * @param fd The registered file descriptor will be written to this address.
+ *
+ * @return  ESP_OK if the registration is successful,
+ *          ESP_ERR_NO_MEM if too many file descriptors are registered,
+ *          ESP_ERR_INVALID_ARG if the arguments are incorrect.
+ */
+esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd);
+
 /**
  * Special function for unregistering a file descriptor belonging to a VFS
  * registered by esp_vfs_register_with_id.

+ 71 - 0
components/vfs/include/esp_vfs_eventfd.h

@@ -0,0 +1,71 @@
+// Copyright 2021 Espressif Systems (Shanghai) CO 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
+
+#pragma once
+
+#include <stddef.h>
+#include <sys/types.h>
+
+#include "esp_err.h"
+
+#define EFD_SUPPORT_ISR (1 << 4)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Eventfd vfs initialization settings
+ */
+typedef struct {
+    size_t max_fds;     /*!< The maxinum number of eventfds supported */
+} esp_vfs_eventfd_config_t;
+
+#define ESP_VFS_EVENTD_CONFIG_DEFAULT() (esp_vfs_eventfd_config_t) { \
+      .max_fds = 5, \
+};
+
+/**
+ * @brief  Registers the event vfs.
+ *
+ * @return  ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ *          registered.
+ */
+esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config);
+
+/**
+ * @brief  Unregisters the event vfs.
+ *
+ * @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix
+ *         hasn't been registered
+ */
+esp_err_t esp_vfs_eventfd_unregister(void);
+
+/*
+ * @brief Creates an event file descirptor.
+ *
+ * The behavior of read, write and select is the same as man(2) eventfd with
+ * EFD_SEMAPHORE **NOT** specified. A new flag EFD_SUPPORT_ISR has been added.
+ * This flag is required to write to event fds in interrupt handlers. Accessing
+ * the control blocks of event fds with EFD_SUPPORT_ISR will cause interrupts to
+ * be temporarily blocked (e.g. during read, write and beginning and ending of
+ * the * select).
+ *
+ * @return The file descriptor if successful, -1 if error happens.
+ */
+int eventfd(unsigned int initval, int flags);
+
+#ifdef __cplusplus
+}
+#endif

+ 357 - 0
components/vfs/test/test_vfs_eventfd.c

@@ -0,0 +1,357 @@
+// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_vfs_eventfd.h"
+
+#include <errno.h>
+#include <sys/select.h>
+
+#include "driver/timer.h"
+#include "esp_vfs.h"
+#include "freertos/FreeRTOS.h"
+#include "unity.h"
+
+TEST_CASE("eventfd create and close", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+    TEST_ASSERT_EQUAL(0, close(fd));
+
+    fd = eventfd(0, EFD_SUPPORT_ISR);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd reject unknown flags", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 1);
+    TEST_ASSERT_LESS_THAN(0, fd);
+    TEST_ASSERT_EQUAL(EINVAL, errno);
+
+    fd = eventfd(0, INT_MAX);
+    TEST_ASSERT_LESS_THAN(0, fd);
+    TEST_ASSERT_EQUAL(EINVAL, errno);
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd read", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    unsigned int initval = 123;
+    int fd = eventfd(initval, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    uint64_t val = 0;
+    TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(initval, val);
+    TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(0, val);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd read invalid size", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    uint32_t val = 0;
+    TEST_ASSERT_LESS_THAN(0, read(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(EINVAL, errno);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd write invalid size", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    uint32_t val = 0;
+    TEST_ASSERT_LESS_THAN(0, write(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(EINVAL, errno);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd write then read", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    uint64_t val = 123;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(123, val);
+    val = 4;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    val = 5;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
+    TEST_ASSERT_EQUAL(9, val);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+TEST_CASE("eventfd instant select", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    struct timeval zero_time;
+    fd_set read_fds, write_fds, error_fds;
+
+    zero_time.tv_sec = 0;
+    zero_time.tv_usec = 0;
+
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+    FD_SET(fd, &read_fds);
+    int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
+    TEST_ASSERT_EQUAL(0, ret);
+    TEST_ASSERT(!FD_ISSET(fd, &read_fds));
+
+    uint64_t val = 1;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+    FD_SET(fd, &read_fds);
+    ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
+    TEST_ASSERT_EQUAL(1, ret);
+    TEST_ASSERT(FD_ISSET(fd, &read_fds));
+
+    TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+    FD_SET(fd, &read_fds);
+    ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
+    TEST_ASSERT_EQUAL(0, ret);
+    TEST_ASSERT(!FD_ISSET(fd, &read_fds));
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+static void signal_task(void *arg)
+{
+    int fd = *((int *)arg);
+    vTaskDelay(pdMS_TO_TICKS(1000));
+    uint64_t val = 1;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    vTaskDelete(NULL);
+}
+
+TEST_CASE("eventfd signal from task", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd0 = eventfd(0, 0);
+    int fd1 = eventfd(0, 0);
+    int max_fd = fd1 > fd0 ? fd1 : fd0;
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd1);
+
+    xTaskCreate(signal_task, "signal_task", 2048, &fd0, 5, NULL);
+    struct timeval wait_time;
+    struct timeval zero_time;
+    fd_set read_fds;
+    FD_ZERO(&read_fds);
+    FD_SET(fd0, &read_fds);
+    FD_SET(fd1, &read_fds);
+    wait_time.tv_sec = 2;
+    wait_time.tv_usec = 0;
+    zero_time.tv_sec = 0;
+    zero_time.tv_usec = 0;
+
+    int ret = select(max_fd + 1, &read_fds, NULL, NULL, &wait_time);
+    TEST_ASSERT_EQUAL(1, ret);
+    TEST_ASSERT(FD_ISSET(fd0, &read_fds));
+
+    uint64_t val = 1;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd1, &val, sizeof(val)));
+
+    FD_ZERO(&read_fds);
+    FD_SET(fd0, &read_fds);
+    FD_SET(fd1, &read_fds);
+    ret = select(max_fd + 1, &read_fds, NULL, NULL, &zero_time);
+    TEST_ASSERT_EQUAL(2, ret);
+    TEST_ASSERT(FD_ISSET(fd0, &read_fds));
+    TEST_ASSERT(FD_ISSET(fd1, &read_fds));
+
+    TEST_ASSERT_EQUAL(0, close(fd0));
+    TEST_ASSERT_EQUAL(0, close(fd1));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+static void eventfd_select_test_isr(void *arg)
+{
+    int fd = *((int *)arg);
+    uint64_t val = 1;
+    timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
+    int ret = write(fd, &val, sizeof(val));
+    assert(ret == sizeof(val));
+}
+
+TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, EFD_SUPPORT_ISR);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    timer_config_t timer_config = {
+        .divider = 16,
+        .counter_dir = TIMER_COUNT_UP,
+        .counter_en = TIMER_PAUSE,
+        .alarm_en = TIMER_ALARM_EN,
+        .auto_reload = false,
+    };
+    TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &timer_config));
+
+    TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL));
+
+    TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_BASE_CLK / 16));
+    TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
+    TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, TIMER_0, eventfd_select_test_isr,
+                                   &fd, ESP_INTR_FLAG_LOWMED, NULL));
+    TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0));
+
+    struct timeval wait_time;
+    fd_set read_fds, write_fds, error_fds;
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+    FD_SET(fd, &read_fds);
+    wait_time.tv_sec = 2;
+    wait_time.tv_usec = 0;
+
+    FD_SET(fd, &read_fds);
+    int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time);
+    TEST_ASSERT_EQUAL(1, ret);
+    TEST_ASSERT(FD_ISSET(fd, &read_fds));
+    timer_deinit(TIMER_GROUP_0, TIMER_0);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+static void close_task(void *arg)
+{
+    int fd = *((int *)arg);
+    vTaskDelay(pdMS_TO_TICKS(1000));
+    TEST_ASSERT_EQUAL(0, close(fd));
+    vTaskDelete(NULL);
+}
+
+TEST_CASE("eventfd select closed fd", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    xTaskCreate(close_task, "close_task", 2048, &fd, 5, NULL);
+    struct timeval wait_time;
+    fd_set read_fds, write_fds, error_fds;
+    FD_ZERO(&read_fds);
+    FD_ZERO(&write_fds);
+    FD_ZERO(&error_fds);
+    FD_SET(fd, &read_fds);
+    FD_SET(fd, &error_fds);
+    wait_time.tv_sec = 2;
+    wait_time.tv_usec = 0;
+
+    int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time);
+    TEST_ASSERT_EQUAL(1, ret);
+    TEST_ASSERT(FD_ISSET(fd, &error_fds));
+
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}
+
+typedef struct {
+    xQueueHandle queue;
+    int fd;
+} select_task_args_t;
+
+static void select_task(void *arg)
+{
+    select_task_args_t *select_arg = (select_task_args_t *)arg;
+    int fd = select_arg->fd;
+    struct timeval wait_time;
+    fd_set read_fds;
+    FD_ZERO(&read_fds);
+    FD_SET(fd, &read_fds);
+    wait_time.tv_sec = 2;
+    wait_time.tv_usec = 0;
+
+    int ret = select(fd + 1, &read_fds, NULL, NULL, &wait_time);
+    assert(ret == 1);
+    xQueueSend(select_arg->queue, select_arg, 0);
+    vTaskDelete(NULL);
+}
+
+TEST_CASE("eventfd multiple selects", "[vfs][eventfd]")
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    TEST_ESP_OK(esp_vfs_eventfd_register(&config));
+
+    int fd = eventfd(0, 0);
+    TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
+
+    select_task_args_t args = {
+        .queue = xQueueCreate(10, sizeof(select_task_args_t)),
+        .fd = fd,
+    };
+    select_task_args_t ret_args;
+
+    xTaskCreate(select_task, "select_task0", 2048, &args, 5, NULL);
+    xTaskCreate(select_task, "select_task1", 2048, &args, 5, NULL);
+
+    uint64_t val = 1;
+    TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
+    vTaskDelay(pdMS_TO_TICKS(100));
+
+    TEST_ASSERT(xQueueReceive(args.queue, &ret_args, 0));
+    TEST_ASSERT_EQUAL(ret_args.fd, fd);
+    TEST_ASSERT(xQueueReceive(args.queue, &ret_args, 0));
+    TEST_ASSERT_EQUAL(ret_args.fd, fd);
+
+    vQueueDelete(args.queue);
+    TEST_ASSERT_EQUAL(0, close(fd));
+    TEST_ESP_OK(esp_vfs_eventfd_unregister());
+}

+ 54 - 32
components/vfs/vfs.c

@@ -172,6 +172,27 @@ esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t
     return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, vfs_id);
 }
 
+esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id)
+{
+    if (vfs_id < 0 || vfs_id >= MAX_FDS || s_vfs[vfs_id] == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    vfs_entry_t* vfs = s_vfs[vfs_id];
+    free(vfs);
+    s_vfs[vfs_id] = NULL;
+
+    _lock_acquire(&s_fd_table_lock);
+    // Delete all references from the FD lookup-table
+    for (int j = 0; j < VFS_MAX_COUNT; ++j) {
+        if (s_fd_table[j].vfs_index == vfs_id) {
+            s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
+        }
+    }
+    _lock_release(&s_fd_table_lock);
+
+    return ESP_OK;
+}
+
 esp_err_t esp_vfs_unregister(const char* base_path)
 {
     const size_t base_path_len = strlen(base_path);
@@ -182,28 +203,22 @@ esp_err_t esp_vfs_unregister(const char* base_path)
         }
         if (base_path_len == vfs->path_prefix_len &&
                 memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) {
-            free(vfs);
-            s_vfs[i] = NULL;
-
-            _lock_acquire(&s_fd_table_lock);
-            // Delete all references from the FD lookup-table
-            for (int j = 0; j < MAX_FDS; ++j) {
-                if (s_fd_table[j].vfs_index == i) {
-                    s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
-                }
-            }
-            _lock_release(&s_fd_table_lock);
-
-            return ESP_OK;
+            return esp_vfs_unregister_with_id(i);
         }
     }
     return ESP_ERR_INVALID_STATE;
 }
 
 esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
+{
+    return esp_vfs_register_fd_with_local_fd(vfs_id, -1, true, fd);
+}
+
+esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd)
 {
     if (vfs_id < 0 || vfs_id >= s_vfs_count || fd == NULL) {
-        ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd(%d, 0x%x)", vfs_id, (int) fd);
+        ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd_with_local_fd(%d, %d, %d, 0x%p)",
+                 vfs_id, local_fd, permanent, fd);
         return ESP_ERR_INVALID_ARG;
     }
 
@@ -211,9 +226,13 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
     _lock_acquire(&s_fd_table_lock);
     for (int i = 0; i < MAX_FDS; ++i) {
         if (s_fd_table[i].vfs_index == -1) {
-            s_fd_table[i].permanent = true;
+            s_fd_table[i].permanent = permanent;
             s_fd_table[i].vfs_index = vfs_id;
-            s_fd_table[i].local_fd = i;
+            if (local_fd >= 0) {
+                s_fd_table[i].local_fd = local_fd;
+            } else {
+                s_fd_table[i].local_fd = i;
+            }
             *fd = i;
             ret = ESP_OK;
             break;
@@ -221,7 +240,8 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
     }
     _lock_release(&s_fd_table_lock);
 
-    ESP_LOGD(TAG, "esp_vfs_register_fd(%d, 0x%x) finished with %s", vfs_id, (int) fd, esp_err_to_name(ret));
+    ESP_LOGD(TAG, "esp_vfs_register_fd_with_local_fd(%d, %d, %d, 0x%p) finished with %s",
+             vfs_id, local_fd, permanent, fd, esp_err_to_name(ret));
 
     return ret;
 }
@@ -806,21 +826,23 @@ static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_s
         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 && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) {
-                    ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i);
-                    FD_SET(fd, readfds);
-                    ++ret;
-                }
-                if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) {
-                    ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i);
-                    FD_SET(fd, writefds);
-                    ++ret;
-                }
-                if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) {
-                    ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i);
-                    FD_SET(fd, errorfds);
-                    ++ret;
+                if (s_fd_table[fd].vfs_index == i) {
+                    const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
+                    if (readfds && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) {
+                        ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i);
+                        FD_SET(fd, readfds);
+                        ++ret;
+                    }
+                    if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) {
+                        ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i);
+                        FD_SET(fd, writefds);
+                        ++ret;
+                    }
+                    if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) {
+                        ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i);
+                        FD_SET(fd, errorfds);
+                        ++ret;
+                    }
                 }
             }
         }

+ 465 - 0
components/vfs/vfs_eventfd.c

@@ -0,0 +1,465 @@
+// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_vfs_eventfd.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/lock.h>
+#include <sys/select.h>
+#include <sys/types.h>
+
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/portmacro.h"
+#include "soc/spinlock.h"
+
+#define FD_INVALID -1
+#define FD_PENDING_SELECT -2
+
+/*
+ * About the event_select_args_t linked list
+ *
+ * Each event_select_args_t structure records a pending select from a select call
+ * on a file descriptor.
+ *
+ * For each select() call, we form a linked list in end_select_args containing
+ * all the pending selects in this select call.
+ *
+ * For each file descriptor, we form a double linked list in event_context_t::select_args.
+ * This list contains all the pending selects on this file descriptor from
+ * different select() calls.
+ *
+ */
+typedef struct event_select_args_t {
+    int                         fd;
+    fd_set                      *read_fds;
+    fd_set                      *error_fds;
+    esp_vfs_select_sem_t        signal_sem;
+    // linked list node in event_context_t::select_args
+    struct event_select_args_t  *prev_in_fd;
+    struct event_select_args_t  *next_in_fd;
+    // linked list node in end_select_arg
+    struct event_select_args_t  *next_in_args;
+} event_select_args_t;
+
+typedef struct {
+    int                     fd;
+    bool                    support_isr;
+    volatile bool           is_set;
+    volatile uint64_t       value;
+    // a double-linked list for all pending select args with this fd
+    event_select_args_t     *select_args;
+    _lock_t                 lock;
+    // only for event fds that support ISR.
+    spinlock_t              data_spin_lock;
+} event_context_t;
+
+esp_vfs_id_t s_eventfd_vfs_id = -1;
+
+static size_t s_event_size;
+static event_context_t *s_events;
+
+static void trigger_select_for_event(event_context_t *event)
+{
+    event_select_args_t *select_args = event->select_args;
+    while (select_args != NULL) {
+        esp_vfs_select_triggered(select_args->signal_sem);
+        select_args = select_args->next_in_fd;
+    }
+}
+
+static void trigger_select_for_event_isr(event_context_t *event, BaseType_t *task_woken)
+{
+    event_select_args_t *select_args = event->select_args;
+    while (select_args != NULL) {
+        BaseType_t local_woken;
+        esp_vfs_select_triggered_isr(select_args->signal_sem, &local_woken);
+        *task_woken = (local_woken || *task_woken);
+        select_args = select_args->next_in_fd;
+    }
+}
+
+#ifdef CONFIG_VFS_SUPPORT_SELECT
+static esp_err_t event_start_select(int                  nfds,
+                                    fd_set              *readfds,
+                                    fd_set              *writefds,
+                                    fd_set              *exceptfds,
+                                    esp_vfs_select_sem_t signal_sem,
+                                    void               **end_select_args)
+{
+    esp_err_t error = ESP_OK;
+    bool should_trigger = false;
+    nfds = nfds < s_event_size ? nfds : (int)s_event_size;
+    event_select_args_t *select_args_list = NULL;
+
+    // FIXME: end_select_args should be a list to all select args
+
+    for (int i = 0; i < nfds; i++) {
+        _lock_acquire_recursive(&s_events[i].lock);
+        if (s_events[i].fd == i) {
+            if (s_events[i].support_isr) {
+                portENTER_CRITICAL(&s_events[i].data_spin_lock);
+            }
+
+            event_select_args_t *event_select_args =
+                (event_select_args_t *)malloc(sizeof(event_select_args_t));
+            event_select_args->fd = i;
+            event_select_args->signal_sem = signal_sem;
+
+            if (FD_ISSET(i, exceptfds)) {
+                FD_CLR(i, exceptfds);
+                event_select_args->error_fds = exceptfds;
+            } else {
+                event_select_args->error_fds = NULL;
+            }
+            FD_CLR(i, exceptfds);
+            // event fds are always writable
+            if (FD_ISSET(i, writefds)) {
+                should_trigger = true;
+            }
+            if (FD_ISSET(i, readfds)) {
+                event_select_args->read_fds = readfds;
+                if (s_events[i].is_set) {
+                    should_trigger = true;
+                } else {
+                    FD_CLR(i, readfds);
+                }
+            } else {
+                event_select_args->read_fds = NULL;
+            }
+            event_select_args->prev_in_fd = NULL;
+            event_select_args->next_in_fd = s_events[i].select_args;
+            if (s_events[i].select_args) {
+                s_events[i].select_args->prev_in_fd = event_select_args;
+            }
+            event_select_args->next_in_args = select_args_list;
+            select_args_list = event_select_args;
+            s_events[i].select_args = event_select_args;
+
+            if (s_events[i].support_isr) {
+                portEXIT_CRITICAL(&s_events[i].data_spin_lock);
+            }
+        }
+        _lock_release_recursive(&s_events[i].lock);
+    }
+
+    *end_select_args = select_args_list;
+
+    if (should_trigger) {
+        esp_vfs_select_triggered(signal_sem);
+    }
+
+    return error;
+}
+
+static esp_err_t event_end_select(void *end_select_args)
+{
+    event_select_args_t *select_args = (event_select_args_t *)end_select_args;
+
+    while (select_args != NULL) {
+        event_context_t *event = &s_events[select_args->fd];
+
+        _lock_acquire_recursive(&event->lock);
+        if (event->support_isr) {
+            portENTER_CRITICAL(&event->data_spin_lock);
+        }
+
+        if (event->fd != select_args->fd) { // already closed
+            if (select_args->error_fds) {
+                FD_SET(select_args->fd, select_args->error_fds);
+            }
+        } else {
+            if (select_args->read_fds && event->is_set) {
+                FD_SET(select_args->fd, select_args->read_fds);
+            }
+        }
+
+        event_select_args_t *prev_in_fd = select_args->prev_in_fd;
+        event_select_args_t *next_in_fd = select_args->next_in_fd;
+        event_select_args_t *next_in_args = select_args->next_in_args;
+        if (prev_in_fd != NULL) {
+            prev_in_fd->next_in_fd = next_in_fd;
+        } else {
+            event->select_args = next_in_fd;
+        }
+        if (next_in_fd != NULL) {
+            next_in_fd->prev_in_fd = prev_in_fd;
+        }
+        if (prev_in_fd == NULL && next_in_fd == NULL) { // The last pending select
+            if (event->fd == FD_PENDING_SELECT) {
+                event->fd = FD_INVALID;
+            }
+        }
+
+        if (event->support_isr) {
+            portEXIT_CRITICAL(&event->data_spin_lock);
+        }
+        _lock_release_recursive(&event->lock);
+
+        free(select_args);
+        select_args = next_in_args;
+    }
+
+    return ESP_OK;
+}
+#endif // CONFIG_VFS_SUPPORT_SELECT
+
+static ssize_t signal_event_fd_from_isr(int fd, const void *data, size_t size)
+{
+    BaseType_t task_woken = pdFALSE;
+    const uint64_t *val = (const uint64_t *)data;
+    ssize_t ret = size;
+
+    portENTER_CRITICAL_ISR(&s_events[fd].data_spin_lock);
+
+    if (s_events[fd].fd == fd) {
+        s_events[fd].is_set = true;
+        s_events[fd].value += *val;
+        trigger_select_for_event_isr(&s_events[fd], &task_woken);
+    } else {
+        errno = EBADF;
+        ret = -1;
+    }
+
+    portEXIT_CRITICAL_ISR(&s_events[fd].data_spin_lock);
+
+    if (task_woken) {
+        portYIELD_FROM_ISR();
+    }
+    return ret;
+}
+
+static ssize_t event_write(int fd, const void *data, size_t size)
+{
+    ssize_t ret = -1;
+
+    if (fd >= s_event_size || data == NULL || size != sizeof(uint64_t)) {
+        errno = EINVAL;
+        return ret;
+    }
+    if (size != sizeof(uint64_t)) {
+        errno = EINVAL;
+        return ret;
+    }
+
+    if (xPortInIsrContext()) {
+        ret = signal_event_fd_from_isr(fd, data, size);
+    } else {
+        const uint64_t *val = (const uint64_t *)data;
+
+        _lock_acquire_recursive(&s_events[fd].lock);
+        if (s_events[fd].support_isr) {
+            portENTER_CRITICAL(&s_events[fd].data_spin_lock);
+        }
+
+        if (s_events[fd].fd == fd) {
+            s_events[fd].is_set = true;
+            s_events[fd].value += *val;
+            ret = size;
+            trigger_select_for_event(&s_events[fd]);
+
+            if (s_events[fd].support_isr) {
+                portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
+            }
+        } else {
+            errno = EBADF;
+            ret = -1;
+        }
+        _lock_release_recursive(&s_events[fd].lock);
+    }
+    return ret;
+}
+
+static ssize_t event_read(int fd, void *data, size_t size)
+{
+    ssize_t ret = -1;
+
+    if (fd >= s_event_size || data == NULL || size != sizeof(uint64_t)) {
+        errno = EINVAL;
+        return ret;
+    }
+
+    uint64_t *val = (uint64_t *)data;
+
+    _lock_acquire_recursive(&s_events[fd].lock);
+    if (s_events[fd].support_isr) {
+        portENTER_CRITICAL(&s_events[fd].data_spin_lock);
+    }
+
+    if (s_events[fd].fd == fd) {
+        *val = s_events[fd].value;
+        s_events[fd].is_set = false;
+        ret = size;
+        s_events[fd].value = 0;
+    } else {
+        errno = EBADF;
+        ret = -1;
+    }
+
+    if (s_events[fd].support_isr) {
+        portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
+    }
+    _lock_release_recursive(&s_events[fd].lock);
+
+    return ret;
+}
+
+static int event_close(int fd)
+{
+    int ret = -1;
+
+    if (fd >= s_event_size) {
+        errno = EINVAL;
+        return ret;
+    }
+
+    _lock_acquire_recursive(&s_events[fd].lock);
+    if (s_events[fd].fd == fd) {
+        if (s_events[fd].support_isr) {
+            portENTER_CRITICAL(&s_events[fd].data_spin_lock);
+        }
+        if (s_events[fd].select_args == NULL) {
+            s_events[fd].fd = FD_INVALID;
+        } else {
+            s_events[fd].fd = FD_PENDING_SELECT;
+            trigger_select_for_event(&s_events[fd]);
+        }
+        s_events[fd].value = 0;
+        if (s_events[fd].support_isr) {
+            portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
+        }
+        ret = 0;
+    } else {
+        errno = EBADF;
+    }
+    _lock_release_recursive(&s_events[fd].lock);
+
+    return ret;
+}
+
+esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config)
+{
+    if (config == NULL || config->max_fds >= MAX_FDS) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (s_eventfd_vfs_id != -1) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    s_event_size = config->max_fds;
+    s_events = (event_context_t *)calloc(s_event_size, sizeof(event_context_t));
+    for (size_t i = 0; i < s_event_size; i++) {
+        _lock_init_recursive(&s_events[i].lock);
+        s_events[i].fd = FD_INVALID;
+    }
+
+    esp_vfs_t vfs = {
+        .flags        = ESP_VFS_FLAG_DEFAULT,
+        .write        = &event_write,
+        .close        = &event_close,
+        .read         = &event_read,
+#ifdef CONFIG_VFS_SUPPORT_SELECT
+        .start_select = &event_start_select,
+        .end_select   = &event_end_select,
+#endif
+    };
+    return esp_vfs_register_with_id(&vfs, NULL, &s_eventfd_vfs_id);
+}
+
+esp_err_t esp_vfs_eventfd_unregister(void)
+{
+    if (s_eventfd_vfs_id == -1) {
+        return ESP_ERR_INVALID_STATE;
+    }
+    esp_err_t error = esp_vfs_unregister_with_id(s_eventfd_vfs_id);
+    if (error == ESP_OK) {
+        s_eventfd_vfs_id = -1;
+    }
+    for (size_t i = 0; i < s_event_size; i++) {
+        _lock_close_recursive(&s_events[i].lock);
+    }
+    free(s_events);
+    return error;
+}
+
+int eventfd(unsigned int initval, int flags)
+{
+    int fd = FD_INVALID;
+    int global_fd = FD_INVALID;
+    esp_err_t error = ESP_OK;
+
+    if ((flags & (~EFD_SUPPORT_ISR)) != 0) {
+        errno = EINVAL;
+        return FD_INVALID;
+    }
+    if (s_eventfd_vfs_id == -1) {
+        errno = EACCES;
+        return FD_INVALID;
+    }
+
+    for (size_t i = 0; i < s_event_size; i++) {
+        _lock_acquire_recursive(&s_events[i].lock);
+        if (s_events[i].fd == FD_INVALID) {
+
+            error = esp_vfs_register_fd_with_local_fd(s_eventfd_vfs_id, i, /*permanent=*/false, &global_fd);
+            if (error != ESP_OK) {
+                _lock_release_recursive(&s_events[i].lock);
+                break;
+            }
+
+            bool support_isr = flags & EFD_SUPPORT_ISR;
+            fd = i;
+            s_events[i].fd = i;
+            s_events[i].support_isr = support_isr;
+            spinlock_initialize(&s_events[i].data_spin_lock);
+
+            if (support_isr) {
+                portENTER_CRITICAL(&s_events[i].data_spin_lock);
+            }
+            s_events[i].is_set = false;
+            s_events[i].value = initval;
+            s_events[i].select_args = NULL;
+            if (support_isr) {
+                portEXIT_CRITICAL(&s_events[i].data_spin_lock);
+            }
+            _lock_release_recursive(&s_events[i].lock);
+            break;
+        }
+        _lock_release_recursive(&s_events[i].lock);
+    }
+
+    switch (error) {
+    case ESP_OK:
+        fd = global_fd;
+        break;
+    case ESP_ERR_NO_MEM:
+        errno = ENOMEM;
+        break;
+    case ESP_ERR_INVALID_ARG:
+        errno = EINVAL;
+        break;
+    default:
+        errno = EIO;
+        break;
+    }
+
+    return fd;
+}

+ 1 - 0
docs/doxygen/Doxyfile_common

@@ -157,6 +157,7 @@ INPUT = \
     $(IDF_PATH)/components/openthread/include/openthread-core-esp32x-config.h \
     $(IDF_PATH)/components/vfs/include/esp_vfs.h \
     $(IDF_PATH)/components/vfs/include/esp_vfs_dev.h \
+    $(IDF_PATH)/components/vfs/include/esp_vfs_eventfd.h \
     $(IDF_PATH)/components/vfs/include/esp_vfs_semihost.h \
     $(IDF_PATH)/components/fatfs/vfs/esp_vfs_fat.h \
     $(IDF_PATH)/components/fatfs/diskio/diskio_impl.h \

+ 1 - 0
docs/en/api-reference/storage/vfs.rst

@@ -7,3 +7,4 @@ API Reference
 
 .. include-build-file:: inc/esp_vfs_dev.inc
 
+.. include-build-file:: inc/esp_vfs_eventfd.inc

+ 6 - 0
examples/system/eventfd/CMakeLists.txt

@@ -0,0 +1,6 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(eventfd)

+ 8 - 0
examples/system/eventfd/Makefile

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

+ 74 - 0
examples/system/eventfd/README.md

@@ -0,0 +1,74 @@
+# eventfd example
+
+The example demonstrates the use of `eventfd()` to collect events from other tasks and ISRs in a
+`select()` based main loop.  The example starts two tasks and installs a timer interrupt handler:
+1. The first task writes to the first `eventfd` periodically.
+2. The timer interrupt handler writes to the second `eventfd`.
+3. The second task collects the event from two fds with a `select()` loop.
+
+## How to use example
+
+### Hardware Required
+
+This example should be able to run on any commonly available ESP32, ESP32S2, ESP32S3 or ESP32C3 development board.
+
+### Configure the project
+
+```
+idf.py menuconfig
+```
+
+The default config will work.
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+The following log output should appear when the example runs (note that the bootloader log has been omitted).
+
+```
+I (4310) eventfd_example: Time: 1.99s
+I (4310) eventfd_example: Select timeout
+I (4310) eventfd_example: =================================
+I (4310) eventfd_example: Select timeouted for 1 times
+I (4320) eventfd_example: Timer triggerred for 0 times
+I (4320) eventfd_example: Progress triggerred for 0 times
+I (4330) eventfd_example: =================================
+I (4810) eventfd_example: Time: 2.50s
+I (4810) eventfd_example: TimerEvent fd event triggered
+I (5810) eventfd_example: Time: 3.49s
+I (5810) eventfd_example: Progress fd event triggered
+I (7310) eventfd_example: Time: 5.00s
+I (7310) eventfd_example: TimerEvent fd event triggered
+I (9310) eventfd_example: Time: 6.99s
+I (9310) eventfd_example: Select timeout
+I (9310) eventfd_example: Time: 6.99s
+I (9310) eventfd_example: Progress fd event triggered
+I (9810) eventfd_example: Time: 7.50s
+I (9810) eventfd_example: TimerEvent fd event triggered
+I (11810) eventfd_example: Time: 9.49s
+I (11810) eventfd_example: Select timeout
+I (12310) eventfd_example: Time: 10.00s
+I (12310) eventfd_example: TimerEvent fd event triggered
+I (12810) eventfd_example: Time: 10.49s
+I (12810) eventfd_example: Progress fd event triggered
+I (14810) eventfd_example: Time: 12.49s
+I (14810) eventfd_example: Select timeout
+I (14810) eventfd_example: =================================
+I (14810) eventfd_example: Select timeouted for 4 times
+I (14820) eventfd_example: Timer triggerred for 4 times
+I (14820) eventfd_example: Progress triggerred for 3 times
+I (14830) eventfd_example: =================================
+```

+ 29 - 0
examples/system/eventfd/example_test.py

@@ -0,0 +1,29 @@
+from __future__ import unicode_literals
+
+import os
+
+import ttfw_idf
+from tiny_test_fw import Env, Utility
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
+def test_examples_eventfd(env, extra_data):
+    # type: (Env, None) -> None
+
+    dut = env.get_dut('eventfd', 'examples/system/eventfd')
+    dut.start_app()
+
+    dut.expect('cpu_start: Starting scheduler', timeout=30)
+
+    exp_list = [
+        'eventfd_example: Select timeouted for 4 times',
+        'eventfd_example: Timer triggerred for 4 times',
+        'eventfd_example: Progress triggerred for 3 times',
+    ]
+
+    Utility.console_log('Expecting:{}{}'.format(os.linesep, os.linesep.join(exp_list)))
+    dut.expect_all(*exp_list, timeout=60)
+
+
+if __name__ == '__main__':
+    test_examples_eventfd()

+ 3 - 0
examples/system/eventfd/main/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "eventfd_example.c"
+                       LDFRAGMENTS linker.lf
+                       INCLUDE_DIRS ".")

+ 6 - 0
examples/system/eventfd/main/component.mk

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

+ 166 - 0
examples/system/eventfd/main/eventfd_example.c

@@ -0,0 +1,166 @@
+/* eventfd 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/select.h>
+
+#include "driver/timer.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "esp_vfs_dev.h"
+#include "esp_vfs_eventfd.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "hal/timer_types.h"
+
+#define TIMER_DIVIDER         16
+#define TIMER_SCALE           (TIMER_BASE_CLK / TIMER_DIVIDER)
+#define MS_PER_S              1000
+#define TIMER_INTERVAL_SEC    2.5
+#define TEST_WITHOUT_RELOAD   0
+#define PROGRESS_INTERVAL_MS  3500
+#define TIMER_SIGNAL          1
+#define PROGRESS_SIGNAL       2
+
+static const char *TAG = "eventfd_example";
+
+int s_timer_fd;
+int s_progress_fd;
+
+static void eventfd_timer_group0_isr(void *para)
+{
+    timer_spinlock_take(TIMER_GROUP_0);
+    int timer_idx = (int) para;
+
+    uint32_t timer_intr = timer_group_get_intr_status_in_isr(TIMER_GROUP_0);
+    uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(TIMER_GROUP_0, timer_idx);
+
+    if (timer_intr & TIMER_INTR_T0) {
+        timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
+        timer_counter_value += (uint64_t) (TIMER_INTERVAL_SEC * TIMER_SCALE);
+        timer_group_set_alarm_value_in_isr(TIMER_GROUP_0, timer_idx, timer_counter_value);
+    }
+
+    timer_group_enable_alarm_in_isr(TIMER_GROUP_0, timer_idx);
+
+    uint64_t signal = TIMER_SIGNAL;
+    ssize_t val = write(s_timer_fd, &signal, sizeof(signal));
+    assert(val == sizeof(signal));
+    timer_spinlock_give(TIMER_GROUP_0);
+}
+
+static void eventfd_timer_init(int timer_idx, double timer_interval_sec)
+{
+    timer_config_t config = {
+        .divider = TIMER_DIVIDER,
+        .counter_dir = TIMER_COUNT_UP,
+        .counter_en = TIMER_PAUSE,
+        .alarm_en = TIMER_ALARM_EN,
+        .auto_reload = false,
+    };
+    ESP_ERROR_CHECK(timer_init(TIMER_GROUP_0, timer_idx, &config));
+
+    ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL));
+
+    ESP_ERROR_CHECK(timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE));
+    ESP_ERROR_CHECK(timer_enable_intr(TIMER_GROUP_0, timer_idx));
+    ESP_ERROR_CHECK(timer_isr_register(TIMER_GROUP_0, timer_idx, eventfd_timer_group0_isr,
+                                       NULL, ESP_INTR_FLAG_IRAM, NULL));
+
+    ESP_ERROR_CHECK(timer_start(TIMER_GROUP_0, timer_idx));
+}
+
+static void worker_task(void *arg)
+{
+    while (true) {
+        vTaskDelay(pdMS_TO_TICKS(PROGRESS_INTERVAL_MS));
+        uint64_t signal = PROGRESS_SIGNAL;
+        ssize_t val = write(s_progress_fd, &signal, sizeof(signal));
+        assert(val == sizeof(signal));
+    }
+    vTaskDelete(NULL);
+}
+
+static void collector_task(void *arg)
+{
+    esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_vfs_eventfd_register(&config));
+
+    s_timer_fd = eventfd(0, EFD_SUPPORT_ISR);
+    s_progress_fd = eventfd(0, 0);
+    assert(s_timer_fd > 0);
+    assert(s_progress_fd > 0);
+
+    int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd;
+    int select_timeout_count = 0;
+    int timer_trigger_count = 0;
+    int progress_trigger_count = 0;
+
+    for (size_t i = 0; ; i++) {
+        struct timeval timeout;
+        uint64_t signal;
+
+        timeout.tv_sec = 2;
+        timeout.tv_usec = 0;
+
+        fd_set readfds;
+        FD_ZERO(&readfds);
+        FD_SET(s_timer_fd, &readfds);
+        FD_SET(s_progress_fd, &readfds);
+
+        int num_triggered = select(maxFd + 1, &readfds, NULL, NULL, &timeout);
+        assert(num_triggered >= 0);
+
+        uint64_t task_counter_value;
+        timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value);
+        ESP_LOGI(TAG, "Time: %.2fs", (double)task_counter_value / TIMER_SCALE);
+
+        if (FD_ISSET(s_progress_fd, &readfds)) {
+            ssize_t ret = read(s_progress_fd, &signal, sizeof(signal));
+            assert(ret == sizeof(signal));
+            assert(signal == PROGRESS_SIGNAL);
+            progress_trigger_count++;
+            ESP_LOGI(TAG, "Progress fd event triggered");
+        }
+        if (FD_ISSET(s_timer_fd, &readfds)) {
+            ssize_t ret = read(s_timer_fd, &signal, sizeof(signal));
+            assert(ret == sizeof(signal));
+            assert(signal == TIMER_SIGNAL);
+            timer_trigger_count++;
+            ESP_LOGI(TAG, "TimerEvent fd event triggered");
+        }
+        if (num_triggered == 0) {
+            select_timeout_count++;
+            ESP_LOGI(TAG, "Select timeout");
+        }
+
+        if (i % 10 == 0) {
+            ESP_LOGI(TAG, "=================================");
+            ESP_LOGI(TAG, "Select timeouted for %d times", select_timeout_count);
+            ESP_LOGI(TAG, "Timer triggerred for %d times", timer_trigger_count);
+            ESP_LOGI(TAG, "Progress triggerred for %d times", progress_trigger_count);
+            ESP_LOGI(TAG, "=================================");
+
+        }
+    }
+
+    timer_deinit(TIMER_GROUP_0, TIMER_0);
+    close(s_timer_fd);
+    close(s_progress_fd);
+    esp_vfs_eventfd_unregister();
+    vTaskDelete(NULL);
+}
+
+void app_main(void)
+{
+    eventfd_timer_init(TIMER_0, TIMER_INTERVAL_SEC);
+    xTaskCreate(worker_task, "worker_task", 4 * 1024, NULL, 5, NULL);
+    xTaskCreate(collector_task, "collector_task", 4 * 1024, NULL, 5, NULL);
+}

+ 4 - 0
examples/system/eventfd/main/linker.lf

@@ -0,0 +1,4 @@
+[mapping:main]
+archive: libmain.a
+entries:
+    eventfd_example:eventfd_timer_group0_isr (noflash)