diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index 1a8e5e89a9..e05a276a87 100644 --- a/.gitlab/ci/target-test.yml +++ b/.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 diff --git a/components/vfs/CMakeLists.txt b/components/vfs/CMakeLists.txt index e2e3e1ec58..015cc01065 100644 --- a/components/vfs/CMakeLists.txt +++ b/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) diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 6a4dce7990..4765f14433 100644 --- a/components/vfs/README.rst +++ b/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. diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index ddbabd975c..7ba6364530 100644 --- a/components/vfs/include/esp_vfs.h +++ b/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. diff --git a/components/vfs/include/esp_vfs_eventfd.h b/components/vfs/include/esp_vfs_eventfd.h new file mode 100644 index 0000000000..430112111a --- /dev/null +++ b/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 +#include + +#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 diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c new file mode 100644 index 0000000000..0d051008f1 --- /dev/null +++ b/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 +#include + +#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()); +} diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 2e3d6ef3c8..46890e3e64 100644 --- a/components/vfs/vfs.c +++ b/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; + } } } } diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c new file mode 100644 index 0000000000..f5f3c49ed5 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/docs/doxygen/Doxyfile_common b/docs/doxygen/Doxyfile_common index 5ae279e691..985e6cde9f 100644 --- a/docs/doxygen/Doxyfile_common +++ b/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 \ diff --git a/docs/en/api-reference/storage/vfs.rst b/docs/en/api-reference/storage/vfs.rst index c7311d58ea..4adbd1110c 100644 --- a/docs/en/api-reference/storage/vfs.rst +++ b/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 diff --git a/examples/system/eventfd/CMakeLists.txt b/examples/system/eventfd/CMakeLists.txt new file mode 100644 index 0000000000..25c37e595f --- /dev/null +++ b/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) diff --git a/examples/system/eventfd/Makefile b/examples/system/eventfd/Makefile new file mode 100644 index 0000000000..c26394d601 --- /dev/null +++ b/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 diff --git a/examples/system/eventfd/README.md b/examples/system/eventfd/README.md new file mode 100644 index 0000000000..2cbb444295 --- /dev/null +++ b/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: ================================= +``` diff --git a/examples/system/eventfd/example_test.py b/examples/system/eventfd/example_test.py new file mode 100644 index 0000000000..8cce7fdad6 --- /dev/null +++ b/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() diff --git a/examples/system/eventfd/main/CMakeLists.txt b/examples/system/eventfd/main/CMakeLists.txt new file mode 100644 index 0000000000..dafbf41a79 --- /dev/null +++ b/examples/system/eventfd/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "eventfd_example.c" + LDFRAGMENTS linker.lf + INCLUDE_DIRS ".") diff --git a/examples/system/eventfd/main/component.mk b/examples/system/eventfd/main/component.mk new file mode 100644 index 0000000000..f3cf3c2887 --- /dev/null +++ b/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 diff --git a/examples/system/eventfd/main/eventfd_example.c b/examples/system/eventfd/main/eventfd_example.c new file mode 100644 index 0000000000..727c145a7b --- /dev/null +++ b/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 +#include + +#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); +} diff --git a/examples/system/eventfd/main/linker.lf b/examples/system/eventfd/main/linker.lf new file mode 100644 index 0000000000..c171663891 --- /dev/null +++ b/examples/system/eventfd/main/linker.lf @@ -0,0 +1,4 @@ +[mapping:main] +archive: libmain.a +entries: + eventfd_example:eventfd_timer_group0_isr (noflash)