From f6fd2eb1648ece6139c9995fcb46d25843b57cdd Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 1 Mar 2021 17:51:48 +0800 Subject: [PATCH 1/8] vfs: add eventfd support eventfd imitates the behavior of POSIX api `man(2) eventfd`. This api can be used to pass events to a select based message loop. --- components/vfs/CMakeLists.txt | 1 + components/vfs/include/esp_vfs_eventfd.h | 61 +++++ components/vfs/test/test_vfs_eventfd.c | 184 +++++++++++++ components/vfs/vfs_eventfd.c | 331 +++++++++++++++++++++++ 4 files changed, 577 insertions(+) create mode 100644 components/vfs/include/esp_vfs_eventfd.h create mode 100644 components/vfs/test/test_vfs_eventfd.c create mode 100644 components/vfs/vfs_eventfd.c 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/include/esp_vfs_eventfd.h b/components/vfs/include/esp_vfs_eventfd.h new file mode 100644 index 0000000000..e8e12337b0 --- /dev/null +++ b/components/vfs/include/esp_vfs_eventfd.h @@ -0,0 +1,61 @@ +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +#pragma once + +#include +#include + +#include "esp_err.h" + +#define EFD_SUPPORT_ISR (1 << 4) +#define EVENT_VFS_PREFIX "/dev/event" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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(void); + +/** + * @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..35d2ee504f --- /dev/null +++ b/components/vfs/test/test_vfs_eventfd.c @@ -0,0 +1,184 @@ +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +#include "esp_vfs_eventfd.h" + +#include +#include +#include + +#include "driver/periph_ctrl.h" +#include "driver/timer.h" +#include "esp_err.h" +#include "esp_types.h" +#include "esp_vfs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/projdefs.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "hal/timer_types.h" +#include "unity.h" + +#define TIMER_DIVIDER 16 +#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) +#define TIMER_INTERVAL0_SEC (0.25) +#define TEST_WITHOUT_RELOAD 0 +#define PROGRESS_INTERVAL_MS 350 +#define TIMER_SIGNAL 1 +#define PROGRESS_SIGNAL 2 + +int s_timer_fd; +int s_progress_fd; + +/* + * A simple helper function to print the raw timer counter value + * and the counter value converted to seconds + */ +static void inline print_timer_counter(uint64_t counter_value) +{ + printf("Counter: 0x%08x%08x\n", (uint32_t) (counter_value >> 32), + (uint32_t) (counter_value)); + printf("Time : %.8f s\n", (double) counter_value / TIMER_SCALE); +} + +void IRAM_ATTR 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_INTERVAL0_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, + }; + timer_init(TIMER_GROUP_0, timer_idx, &config); + + timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL); + + timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE); + timer_enable_intr(TIMER_GROUP_0, timer_idx); + timer_isr_register(TIMER_GROUP_0, timer_idx, timer_group0_isr, + (void *) timer_idx, ESP_INTR_FLAG_IRAM, NULL); + + timer_start(TIMER_GROUP_0, timer_idx); +} + +static void eventfd_timer_deinit(int timer_idx) +{ + timer_pause(TIMER_GROUP_0, timer_idx); + timer_deinit(TIMER_GROUP_0, timer_idx); +} + +static void worker_task(void *arg) +{ + for (int i = 0; i < 3; i++) { + 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); +} + +TEST_CASE("Test eventfd triggered correctly", "[vfs][eventfd]") +{ + xTaskCreate(worker_task, "worker_task", 1024, NULL, 5, NULL); + TEST_ESP_OK(esp_vfs_eventfd_register()); + s_timer_fd = eventfd(0, EFD_SUPPORT_ISR); + s_progress_fd = eventfd(0, 0); + int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd; + printf("Timer fd %d progress fd %d\n", s_timer_fd, s_progress_fd); + eventfd_timer_init(TIMER_0, TIMER_INTERVAL0_SEC); + + int selectTimeoutCount = 0; + int timerTriggerCount = 0; + int progressTriggerCount = 0; + + for (size_t i = 0; i < 10; i++) { + struct timeval timeout; + uint64_t signal; + + timeout.tv_sec = 0; + timeout.tv_usec = 200 * 1000; + + fd_set readfds; + fd_set writefds; + fd_set errorfds; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + FD_SET(s_timer_fd, &readfds); + FD_SET(s_progress_fd, &readfds); + + select(maxFd + 1, &readfds, &writefds, &errorfds, &timeout); + + printf("-------- TASK TIME --------\n"); + uint64_t task_counter_value; + timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value); + print_timer_counter(task_counter_value); + + if (FD_ISSET(s_progress_fd, &readfds)) { + ssize_t ret = read(s_progress_fd, &signal, sizeof(signal)); + TEST_ASSERT(ret == sizeof(signal)); + TEST_ASSERT(signal == PROGRESS_SIGNAL); + progressTriggerCount++; + printf("Progress fd\n"); + } else if (FD_ISSET(s_timer_fd, &readfds)) { + ssize_t ret = read(s_timer_fd, &signal, sizeof(signal)); + TEST_ASSERT(ret == sizeof(signal)); + TEST_ASSERT(signal == TIMER_SIGNAL); + timerTriggerCount++; + printf("TimerEvent fd\n"); + } else { + selectTimeoutCount++; + printf("Select timeout\n"); + } + } + + printf("Select timeout: %d\n", selectTimeoutCount); + printf("Timer trigger: %d\n", timerTriggerCount); + printf("Progress trigger: %d\n", progressTriggerCount); + TEST_ASSERT(selectTimeoutCount == 3); + TEST_ASSERT(timerTriggerCount == 4); + TEST_ASSERT(progressTriggerCount == 3); + printf("Test done\n"); + close(s_progress_fd); + close(s_timer_fd); + eventfd_timer_deinit(TIMER_0); + TEST_ESP_OK(esp_vfs_eventfd_unregister()); +} diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c new file mode 100644 index 0000000000..65f4d35a52 --- /dev/null +++ b/components/vfs/vfs_eventfd.c @@ -0,0 +1,331 @@ +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + + +#include "esp_vfs_eventfd.h" + +#include +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_vfs.h" +#include "freertos/FreeRTOS.h" +#include "soc/spinlock.h" + +#define FD_INVALID -1 +#define NUM_EVENT_FDS 5 + +typedef struct { + int fd; + bool support_isr; + volatile bool is_set; + volatile uint64_t value; + fd_set *read_fds; + fd_set *write_fds; + fd_set *error_fds; + esp_vfs_select_sem_t signal_sem; + _lock_t lock; + spinlock_t data_spin_lock; // only for event fds that support ISR. +} Event; + +static Event s_events[NUM_EVENT_FDS]; + +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; + + for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + _lock_acquire_recursive(&s_events[i].lock); + int fd = s_events[i].fd; + + if (fd != FD_INVALID) { + if (s_events[i].support_isr) { + portENTER_CRITICAL(&s_events[i].data_spin_lock); + } + s_events[i].signal_sem = signal_sem; + s_events[i].error_fds = exceptfds; + // event fds shouldn't report error + FD_CLR(fd, exceptfds); + + // event fds are always writable + if (FD_ISSET(fd, writefds)) { + s_events[i].write_fds = writefds; + should_trigger = true; + } + if (FD_ISSET(fd, readfds)) { + s_events[i].read_fds = readfds; + if (s_events[i].is_set) { + s_events[i].is_set = false; + should_trigger = true; + } else { + FD_CLR(fd, readfds); + } + } + + if (s_events[i].support_isr) { + portEXIT_CRITICAL(&s_events[i].data_spin_lock); + } + + } + _lock_release_recursive(&s_events[i].lock); + } + + if (should_trigger) { + esp_vfs_select_triggered(signal_sem); + } + + return error; +} + +static esp_err_t event_end_select(void *end_select_args) +{ + for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + _lock_acquire_recursive(&s_events[i].lock); + if (s_events[i].support_isr) { + portENTER_CRITICAL(&s_events[i].data_spin_lock); + } + memset(&s_events[i].signal_sem, 0, sizeof(s_events[i].signal_sem)); + if (s_events[i].read_fds && s_events[i].is_set) { + FD_SET(s_events[i].fd, s_events[i].read_fds); + s_events[i].is_set = false; + s_events[i].read_fds = NULL; + } + if (s_events[i].write_fds) { + FD_SET(s_events[i].fd, s_events[i].write_fds); + s_events[i].write_fds = NULL; + } + + if (s_events[i].support_isr) { + portEXIT_CRITICAL(&s_events[i].data_spin_lock); + } + _lock_release_recursive(&s_events[i].lock); + } + return ESP_OK; +} + +static int event_open(const char *path, int flags, int mode) +{ + + (void)flags; + (void)mode; + + if (path == NULL || path[0] != '/') { + return -1; + } + + char *endPath; + int fd = strtol(path + 1, &endPath, 10); + + if (endPath == NULL || *endPath != '\0' || fd >= NUM_EVENT_FDS) { + return -1; + } + + return fd; +} + + +ssize_t esp_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; + + portENTER_CRITICAL_ISR(&s_events[fd].data_spin_lock); + + s_events[fd].is_set = true; + s_events[fd].value += *val; + if (s_events[fd].signal_sem.sem != NULL) { + esp_vfs_select_triggered_isr(s_events[fd].signal_sem, &task_woken); + } + + portEXIT_CRITICAL_ISR(&s_events[fd].data_spin_lock); + + if (task_woken) { + portYIELD_FROM_ISR(); + } + return size; +} + +static ssize_t event_write(int fd, const void *data, size_t size) +{ + ssize_t ret = -1; + + if (fd >= NUM_EVENT_FDS || data == NULL || size != sizeof(uint64_t)) { + return ret; + } + if (size != sizeof(uint64_t)) { + return ret; + } + + if (xPortInIsrContext()) { + ret = esp_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); + } + s_events[fd].is_set = true; + s_events[fd].value += *val; + ret = size; + if (s_events[fd].signal_sem.sem != NULL) { + esp_vfs_select_triggered(s_events[fd].signal_sem); + } + if (s_events[fd].support_isr) { + portEXIT_CRITICAL(&s_events[fd].data_spin_lock); + } + + _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 >= NUM_EVENT_FDS) { + return ret; + } + if (size != sizeof(uint64_t)) { + return ret; + } + + _lock_acquire_recursive(&s_events[fd].lock); + + if (s_events[fd].fd == fd) { + uint64_t *val = (uint64_t *)data; + if (s_events[fd].support_isr) { + portENTER_CRITICAL(&s_events[fd].data_spin_lock); + } + *val = s_events[fd].value; + ret = size; + s_events[fd].value = 0; + 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 >= NUM_EVENT_FDS) { + 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); + } + s_events[fd].fd = FD_INVALID; + memset(&s_events[fd].signal_sem, 0, sizeof(s_events[fd].signal_sem)); + s_events[fd].value = 0; + if (s_events[fd].support_isr) { + portEXIT_CRITICAL(&s_events[fd].data_spin_lock); + } + ret = 0; + } + + _lock_release_recursive(&s_events[fd].lock); + _lock_close(&s_events[fd].lock); + return ret; +} + +esp_err_t esp_vfs_eventfd_register(void) +{ + for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + s_events[i].fd = FD_INVALID; + } + + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &event_write, + .open = &event_open, + .fstat = NULL, + .close = &event_close, + .read = &event_read, + .fcntl = NULL, + .fsync = NULL, + .access = NULL, + .start_select = &event_start_select, + .end_select = &event_end_select, +#ifdef CONFIG_SUPPORT_TERMIOS + .tcsetattr = NULL, + .tcgetattr = NULL, + .tcdrain = NULL, + .tcflush = NULL, +#endif // CONFIG_SUPPORT_TERMIOS + }; + return esp_vfs_register(EVENT_VFS_PREFIX, &vfs, NULL); +} + +esp_err_t esp_vfs_eventfd_unregister(void) +{ + return esp_vfs_unregister(EVENT_VFS_PREFIX); +} + +int eventfd(unsigned int initval, int flags) +{ + int fd = -1; + + for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + bool support_isr = flags & EFD_SUPPORT_ISR; + bool has_allocated = false; + + _lock_acquire_recursive(&s_events[i].lock); + if (s_events[i].fd == FD_INVALID) { + 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; + memset(&s_events[i].signal_sem, 0, sizeof(s_events[i].signal_sem)); + if (support_isr) { + portEXIT_CRITICAL(&s_events[i].data_spin_lock); + } + + char fullpath[20]; + snprintf(fullpath, sizeof(fullpath), EVENT_VFS_PREFIX "/%d", s_events[i].fd); + fd = open(fullpath, 0, 0); + has_allocated = true; + } + _lock_release_recursive(&s_events[i].lock); + + if (has_allocated) { + return fd; + } + } + return -1; +} From 30e9345bc7bdfd5f111c5eb62d2685e2cb9874a0 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Tue, 2 Mar 2021 10:12:54 +0800 Subject: [PATCH 2/8] vfs: add unit tests for eventfd --- components/vfs/test/test_vfs_eventfd.c | 355 +++++++++++------- .../vfs/test/test_vfs_eventfd_integration.c | 183 +++++++++ components/vfs/vfs_eventfd.c | 26 +- 3 files changed, 414 insertions(+), 150 deletions(-) create mode 100644 components/vfs/test/test_vfs_eventfd_integration.c diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c index 35d2ee504f..71a02eefec 100644 --- a/components/vfs/test/test_vfs_eventfd.c +++ b/components/vfs/test/test_vfs_eventfd.c @@ -14,171 +14,242 @@ #include "esp_vfs_eventfd.h" -#include -#include +#include #include -#include "driver/periph_ctrl.h" #include "driver/timer.h" -#include "esp_err.h" -#include "esp_types.h" #include "esp_vfs.h" -#include "freertos/FreeRTOS.h" -#include "freertos/portmacro.h" -#include "freertos/projdefs.h" -#include "freertos/queue.h" -#include "freertos/task.h" -#include "hal/timer_types.h" +#include "sys/_stdint.h" #include "unity.h" -#define TIMER_DIVIDER 16 -#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) -#define TIMER_INTERVAL0_SEC (0.25) -#define TEST_WITHOUT_RELOAD 0 -#define PROGRESS_INTERVAL_MS 350 -#define TIMER_SIGNAL 1 -#define PROGRESS_SIGNAL 2 - -int s_timer_fd; -int s_progress_fd; - -/* - * A simple helper function to print the raw timer counter value - * and the counter value converted to seconds - */ -static void inline print_timer_counter(uint64_t counter_value) +TEST_CASE("Test eventfd create and close", "[vfs][eventfd]") { - printf("Counter: 0x%08x%08x\n", (uint32_t) (counter_value >> 32), - (uint32_t) (counter_value)); - printf("Time : %.8f s\n", (double) counter_value / TIMER_SCALE); + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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()); } -void IRAM_ATTR timer_group0_isr(void *para) +TEST_CASE("Test eventfd reject unknown flags", "[vfs][eventfd]") { - timer_spinlock_take(TIMER_GROUP_0); - int timer_idx = (int) para; + TEST_ESP_OK(esp_vfs_eventfd_register()); + int fd = eventfd(0, 1); + TEST_ASSERT_LESS_THAN(0, fd); + TEST_ASSERT_EQUAL(EINVAL, errno); - 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_INTERVAL0_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); + fd = eventfd(0, INT_MAX); + TEST_ASSERT_LESS_THAN(0, fd); + TEST_ASSERT_EQUAL(EINVAL, errno); + TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -static void eventfd_timer_init(int timer_idx, double timer_interval_sec) +TEST_CASE("Test eventfd read", "[vfs][eventfd]") { + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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("Test eventfd read invalid size", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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("Test eventfd write invalid size", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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("Test eventfd write then read", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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("Test eventfd instant select", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + 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; + printf("Write to fd\n"); + 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_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("Test eventfd signal from task", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + int fd = eventfd(0, 0); + TEST_ASSERT_GREATER_OR_EQUAL(0, fd); + + xTaskCreate(signal_task, "signal_task", 2048, &fd, 5, NULL); + struct timeval wait_time; + struct timeval zero_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; + zero_time.tv_sec = 0; + zero_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)); + + 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)); + + uint64_t val; + 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_ESP_OK(esp_vfs_eventfd_unregister()); +} + +static void IRAM_ATTR 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("Test eventfd signal from ISR", "[vfs][eventfd]") +{ + TEST_ESP_OK(esp_vfs_eventfd_register()); + int fd = eventfd(0, EFD_SUPPORT_ISR); + TEST_ASSERT_GREATER_OR_EQUAL(0, fd); + timer_config_t config = { - .divider = TIMER_DIVIDER, + .divider = 16, .counter_dir = TIMER_COUNT_UP, .counter_en = TIMER_PAUSE, .alarm_en = TIMER_ALARM_EN, .auto_reload = false, }; - timer_init(TIMER_GROUP_0, timer_idx, &config); + TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &config)); - timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL); + TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL)); - timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE); - timer_enable_intr(TIMER_GROUP_0, timer_idx); - timer_isr_register(TIMER_GROUP_0, timer_idx, timer_group0_isr, - (void *) timer_idx, ESP_INTR_FLAG_IRAM, NULL); + 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_IRAM, NULL)); + TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); - timer_start(TIMER_GROUP_0, timer_idx); -} + 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; -static void eventfd_timer_deinit(int timer_idx) -{ - timer_pause(TIMER_GROUP_0, timer_idx); - timer_deinit(TIMER_GROUP_0, timer_idx); -} - -static void worker_task(void *arg) -{ - for (int i = 0; i < 3; i++) { - 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); -} - -TEST_CASE("Test eventfd triggered correctly", "[vfs][eventfd]") -{ - xTaskCreate(worker_task, "worker_task", 1024, NULL, 5, NULL); - TEST_ESP_OK(esp_vfs_eventfd_register()); - s_timer_fd = eventfd(0, EFD_SUPPORT_ISR); - s_progress_fd = eventfd(0, 0); - int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd; - printf("Timer fd %d progress fd %d\n", s_timer_fd, s_progress_fd); - eventfd_timer_init(TIMER_0, TIMER_INTERVAL0_SEC); - - int selectTimeoutCount = 0; - int timerTriggerCount = 0; - int progressTriggerCount = 0; - - for (size_t i = 0; i < 10; i++) { - struct timeval timeout; - uint64_t signal; - - timeout.tv_sec = 0; - timeout.tv_usec = 200 * 1000; - - fd_set readfds; - fd_set writefds; - fd_set errorfds; - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&errorfds); - - FD_SET(s_timer_fd, &readfds); - FD_SET(s_progress_fd, &readfds); - - select(maxFd + 1, &readfds, &writefds, &errorfds, &timeout); - - printf("-------- TASK TIME --------\n"); - uint64_t task_counter_value; - timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value); - print_timer_counter(task_counter_value); - - if (FD_ISSET(s_progress_fd, &readfds)) { - ssize_t ret = read(s_progress_fd, &signal, sizeof(signal)); - TEST_ASSERT(ret == sizeof(signal)); - TEST_ASSERT(signal == PROGRESS_SIGNAL); - progressTriggerCount++; - printf("Progress fd\n"); - } else if (FD_ISSET(s_timer_fd, &readfds)) { - ssize_t ret = read(s_timer_fd, &signal, sizeof(signal)); - TEST_ASSERT(ret == sizeof(signal)); - TEST_ASSERT(signal == TIMER_SIGNAL); - timerTriggerCount++; - printf("TimerEvent fd\n"); - } else { - selectTimeoutCount++; - printf("Select timeout\n"); - } - } - - printf("Select timeout: %d\n", selectTimeoutCount); - printf("Timer trigger: %d\n", timerTriggerCount); - printf("Progress trigger: %d\n", progressTriggerCount); - TEST_ASSERT(selectTimeoutCount == 3); - TEST_ASSERT(timerTriggerCount == 4); - TEST_ASSERT(progressTriggerCount == 3); - printf("Test done\n"); - close(s_progress_fd); - close(s_timer_fd); - eventfd_timer_deinit(TIMER_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_ESP_OK(esp_vfs_eventfd_unregister()); } diff --git a/components/vfs/test/test_vfs_eventfd_integration.c b/components/vfs/test/test_vfs_eventfd_integration.c new file mode 100644 index 0000000000..4e59822127 --- /dev/null +++ b/components/vfs/test/test_vfs_eventfd_integration.c @@ -0,0 +1,183 @@ +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +#include "esp_vfs_eventfd.h" + +#include +#include +#include + +#include "driver/periph_ctrl.h" +#include "driver/timer.h" +#include "esp_err.h" +#include "esp_types.h" +#include "esp_vfs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "hal/timer_types.h" +#include "unity.h" + +#define TIMER_DIVIDER 16 +#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) +#define TIMER_INTERVAL0_SEC (0.25) +#define TEST_WITHOUT_RELOAD 0 +#define PROGRESS_INTERVAL_MS 350 +#define TIMER_SIGNAL 1 +#define PROGRESS_SIGNAL 2 + +int s_timer_fd; +int s_progress_fd; + +/* + * A simple helper function to print the raw timer counter value + * and the counter value converted to seconds + */ +static void inline print_timer_counter(uint64_t counter_value) +{ + printf("Counter: 0x%08x%08x\n", (uint32_t) (counter_value >> 32), + (uint32_t) (counter_value)); + printf("Time : %.8f s\n", (double) counter_value / TIMER_SCALE); +} + +static void IRAM_ATTR 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_INTERVAL0_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, + }; + TEST_ESP_OK(timer_init(TIMER_GROUP_0, timer_idx, &config)); + + TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL)); + + TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE)); + TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, timer_idx)); + TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, timer_idx, eventfd_timer_group0_isr, + (void *) timer_idx, ESP_INTR_FLAG_IRAM, NULL)); + + TEST_ESP_OK(timer_start(TIMER_GROUP_0, timer_idx)); +} + +static void eventfd_timer_deinit(int timer_idx) +{ + timer_pause(TIMER_GROUP_0, timer_idx); + timer_deinit(TIMER_GROUP_0, timer_idx); +} + +static void worker_task(void *arg) +{ + for (int i = 0; i < 3; i++) { + 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); +} + +TEST_CASE("Test eventfd triggered correctly", "[vfs][eventfd]") +{ + xTaskCreate(worker_task, "worker_task", 1024, NULL, 5, NULL); + TEST_ESP_OK(esp_vfs_eventfd_register()); + s_timer_fd = eventfd(0, EFD_SUPPORT_ISR); + s_progress_fd = eventfd(0, 0); + int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd; + printf("Timer fd %d progress fd %d\n", s_timer_fd, s_progress_fd); + TEST_ASSERT_GREATER_OR_EQUAL(0, s_timer_fd); + TEST_ASSERT_GREATER_OR_EQUAL(0, s_progress_fd); + eventfd_timer_init(TIMER_0, TIMER_INTERVAL0_SEC); + + int select_timeout_count = 0; + int timer_trigger_count = 0; + int progress_trigger_count = 0; + + for (size_t i = 0; i < 10; i++) { + struct timeval timeout; + uint64_t signal; + + timeout.tv_sec = 0; + timeout.tv_usec = 200 * 1000; + + fd_set readfds; + fd_set writefds; + fd_set errorfds; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + FD_SET(s_timer_fd, &readfds); + FD_SET(s_progress_fd, &readfds); + + select(maxFd + 1, &readfds, &writefds, &errorfds, &timeout); + + printf("-------- TASK TIME --------\n"); + uint64_t task_counter_value; + TEST_ESP_OK(timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value)); + print_timer_counter(task_counter_value); + + if (FD_ISSET(s_progress_fd, &readfds)) { + ssize_t ret = read(s_progress_fd, &signal, sizeof(signal)); + TEST_ASSERT_EQUAL(ret, sizeof(signal)); + TEST_ASSERT_EQUAL(signal, PROGRESS_SIGNAL); + progress_trigger_count++; + printf("Progress fd\n"); + } else if (FD_ISSET(s_timer_fd, &readfds)) { + ssize_t ret = read(s_timer_fd, &signal, sizeof(signal)); + TEST_ASSERT(ret == sizeof(signal)); + TEST_ASSERT(signal == TIMER_SIGNAL); + timer_trigger_count++; + printf("TimerEvent fd\n"); + } else { + select_timeout_count++; + printf("Select timeout\n"); + } + } + + printf("Select timeout: %d\n", select_timeout_count); + printf("Timer trigger: %d\n", timer_trigger_count); + printf("Progress trigger: %d\n", progress_trigger_count); + TEST_ASSERT_EQUAL(3, select_timeout_count); + TEST_ASSERT_EQUAL(4, timer_trigger_count); + TEST_ASSERT_EQUAL(3, progress_trigger_count); + TEST_ASSERT_EQUAL(0, close(s_progress_fd)); + TEST_ASSERT_EQUAL(0, close(s_timer_fd)); + eventfd_timer_deinit(TIMER_0); + TEST_ESP_OK(esp_vfs_eventfd_unregister()); +} diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c index 65f4d35a52..3aadda17df 100644 --- a/components/vfs/vfs_eventfd.c +++ b/components/vfs/vfs_eventfd.c @@ -15,6 +15,7 @@ #include "esp_vfs_eventfd.h" +#include #include #include #include @@ -59,7 +60,7 @@ static esp_err_t event_start_select(int nfds, _lock_acquire_recursive(&s_events[i].lock); int fd = s_events[i].fd; - if (fd != FD_INVALID) { + if (fd != FD_INVALID && fd < nfds) { if (s_events[i].support_isr) { portENTER_CRITICAL(&s_events[i].data_spin_lock); } @@ -76,7 +77,6 @@ static esp_err_t event_start_select(int nfds, if (FD_ISSET(fd, readfds)) { s_events[i].read_fds = readfds; if (s_events[i].is_set) { - s_events[i].is_set = false; should_trigger = true; } else { FD_CLR(fd, readfds); @@ -108,7 +108,6 @@ static esp_err_t event_end_select(void *end_select_args) memset(&s_events[i].signal_sem, 0, sizeof(s_events[i].signal_sem)); if (s_events[i].read_fds && s_events[i].is_set) { FD_SET(s_events[i].fd, s_events[i].read_fds); - s_events[i].is_set = false; s_events[i].read_fds = NULL; } if (s_events[i].write_fds) { @@ -145,7 +144,7 @@ static int event_open(const char *path, int flags, int mode) } -ssize_t esp_signal_event_fd_from_isr(int fd, const void *data, size_t size) +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; @@ -171,14 +170,16 @@ static ssize_t event_write(int fd, const void *data, size_t size) ssize_t ret = -1; if (fd >= NUM_EVENT_FDS || data == NULL || size != sizeof(uint64_t)) { + errno = EINVAL; return ret; } if (size != sizeof(uint64_t)) { + errno = EINVAL; return ret; } if (xPortInIsrContext()) { - ret = esp_signal_event_fd_from_isr(fd, data, size); + 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); @@ -206,9 +207,11 @@ static ssize_t event_read(int fd, void *data, size_t size) ssize_t ret = -1; if (fd >= NUM_EVENT_FDS) { + errno = EINVAL; return ret; } if (size != sizeof(uint64_t)) { + errno = EINVAL; return ret; } @@ -220,6 +223,7 @@ static ssize_t event_read(int fd, void *data, size_t size) portENTER_CRITICAL(&s_events[fd].data_spin_lock); } *val = s_events[fd].value; + s_events[fd].is_set = false; ret = size; s_events[fd].value = 0; if (s_events[fd].support_isr) { @@ -255,7 +259,7 @@ static int event_close(int fd) } _lock_release_recursive(&s_events[fd].lock); - _lock_close(&s_events[fd].lock); + _lock_close_recursive(&s_events[fd].lock); return ret; } @@ -294,12 +298,18 @@ esp_err_t esp_vfs_eventfd_unregister(void) int eventfd(unsigned int initval, int flags) { - int fd = -1; + int fd = FD_INVALID; + + if ((flags & (~EFD_SUPPORT_ISR)) != 0) { + errno = EINVAL; + return FD_INVALID; + } for (size_t i = 0; i < NUM_EVENT_FDS; i++) { bool support_isr = flags & EFD_SUPPORT_ISR; bool has_allocated = false; + _lock_init_recursive(&s_events[i].lock); _lock_acquire_recursive(&s_events[i].lock); if (s_events[i].fd == FD_INVALID) { s_events[i].fd = i; @@ -327,5 +337,5 @@ int eventfd(unsigned int initval, int flags) return fd; } } - return -1; + return FD_INVALID; } From 7b911e4641025c126a7327da2a9693da4c3328ba Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 09:44:58 +0800 Subject: [PATCH 3/8] vfs: add support for mutiple selects in eventfd --- .../bluedroid/btc/profile/std/spp/btc_spp.c | 2 +- components/vfs/include/esp_vfs.h | 13 +- components/vfs/include/esp_vfs_eventfd.h | 7 +- components/vfs/test/test_vfs_eventfd.c | 222 +++++++++--- .../vfs/test/test_vfs_eventfd_integration.c | 5 +- components/vfs/vfs.c | 78 ++-- components/vfs/vfs_eventfd.c | 338 ++++++++++++------ 7 files changed, 469 insertions(+), 196 deletions(-) diff --git a/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c b/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c index a8fe25e352..1608892d01 100644 --- a/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c +++ b/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c @@ -151,7 +151,7 @@ static spp_slot_t *spp_malloc_slot(void) break; } if (spp_local_param.spp_mode == ESP_SPP_MODE_VFS) { - if (esp_vfs_register_fd(spp_local_param.spp_vfs_id, &(*slot)->fd) != ESP_OK) { + if (esp_vfs_register_fd(spp_local_param.spp_vfs_id, -1, /*permanent=*/true, &(*slot)->fd) != ESP_OK) { BTC_TRACE_ERROR("%s unable to register fd!", __func__); err_no = 3; break; diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index ddbabd975c..30689d6d89 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -325,18 +325,29 @@ 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. * * @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 permanenent 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(esp_vfs_id_t vfs_id, int *fd); +esp_err_t esp_vfs_register_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 diff --git a/components/vfs/include/esp_vfs_eventfd.h b/components/vfs/include/esp_vfs_eventfd.h index e8e12337b0..90c7b5939b 100644 --- a/components/vfs/include/esp_vfs_eventfd.h +++ b/components/vfs/include/esp_vfs_eventfd.h @@ -20,19 +20,22 @@ #include "esp_err.h" #define EFD_SUPPORT_ISR (1 << 4) -#define EVENT_VFS_PREFIX "/dev/event" #ifdef __cplusplus extern "C" { #endif +typedef struct { + size_t eventfd_max_num; +} esp_vfs_eventfd_config_t; + /** * @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(void); +esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config); /** * @brief Unregisters the event vfs. diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c index 71a02eefec..5d7732b540 100644 --- a/components/vfs/test/test_vfs_eventfd.c +++ b/components/vfs/test/test_vfs_eventfd.c @@ -19,12 +19,15 @@ #include "driver/timer.h" #include "esp_vfs.h" -#include "sys/_stdint.h" +#include "freertos/FreeRTOS.h" #include "unity.h" -TEST_CASE("Test eventfd create and close", "[vfs][eventfd]") +TEST_CASE("eventfd create and close", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + 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)); @@ -35,9 +38,13 @@ TEST_CASE("Test eventfd create and close", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd reject unknown flags", "[vfs][eventfd]") +TEST_CASE("eventfd reject unknown flags", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + int fd = eventfd(0, 1); TEST_ASSERT_LESS_THAN(0, fd); TEST_ASSERT_EQUAL(EINVAL, errno); @@ -48,9 +55,13 @@ TEST_CASE("Test eventfd reject unknown flags", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd read", "[vfs][eventfd]") +TEST_CASE("eventfd read", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + unsigned int initval = 123; int fd = eventfd(initval, 0); TEST_ASSERT_GREATER_OR_EQUAL(0, fd); @@ -64,9 +75,13 @@ TEST_CASE("Test eventfd read", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd read invalid size", "[vfs][eventfd]") +TEST_CASE("eventfd read invalid size", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + int fd = eventfd(0, 0); TEST_ASSERT_GREATER_OR_EQUAL(0, fd); @@ -77,9 +92,13 @@ TEST_CASE("Test eventfd read invalid size", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd write invalid size", "[vfs][eventfd]") +TEST_CASE("eventfd write invalid size", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + int fd = eventfd(0, 0); TEST_ASSERT_GREATER_OR_EQUAL(0, fd); @@ -90,9 +109,13 @@ TEST_CASE("Test eventfd write invalid size", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd write then read", "[vfs][eventfd]") +TEST_CASE("eventfd write then read", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + int fd = eventfd(0, 0); TEST_ASSERT_GREATER_OR_EQUAL(0, fd); @@ -110,9 +133,13 @@ TEST_CASE("Test eventfd write then read", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -TEST_CASE("Test eventfd instant select", "[vfs][eventfd]") +TEST_CASE("eventfd instant select", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); + int fd = eventfd(0, 0); TEST_ASSERT_GREATER_OR_EQUAL(0, fd); @@ -126,12 +153,12 @@ TEST_CASE("Test eventfd instant select", "[vfs][eventfd]") FD_ZERO(&write_fds); FD_ZERO(&error_fds); FD_SET(fd, &read_fds); + printf("fd %d\n", fd); 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; - printf("Write to fd\n"); TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val))); FD_ZERO(&read_fds); FD_ZERO(&write_fds); @@ -149,6 +176,7 @@ TEST_CASE("Test eventfd instant select", "[vfs][eventfd]") 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()); } @@ -161,47 +189,49 @@ static void signal_task(void *arg) vTaskDelete(NULL); } -TEST_CASE("Test eventfd signal from task", "[vfs][eventfd]") +TEST_CASE("eventfd signal from task", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); - int fd = eventfd(0, 0); - TEST_ASSERT_GREATER_OR_EQUAL(0, fd); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); - xTaskCreate(signal_task, "signal_task", 2048, &fd, 5, NULL); + 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, write_fds, error_fds; + fd_set read_fds; FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - FD_ZERO(&error_fds); - FD_SET(fd, &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; - FD_SET(fd, &read_fds); - int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time); + int ret = select(max_fd + 1, &read_fds, NULL, NULL, &wait_time); + printf("Frist select returned\n"); TEST_ASSERT_EQUAL(1, ret); - TEST_ASSERT(FD_ISSET(fd, &read_fds)); + 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_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)); + 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)); - uint64_t val; - 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(fd0)); + TEST_ASSERT_EQUAL(0, close(fd1)); TEST_ESP_OK(esp_vfs_eventfd_unregister()); } @@ -214,20 +244,24 @@ static void IRAM_ATTR eventfd_select_test_isr(void *arg) assert(ret == sizeof(val)); } -TEST_CASE("Test eventfd signal from ISR", "[vfs][eventfd]") +TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]") { - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + 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 config = { + 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, &config)); + 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)); @@ -251,5 +285,99 @@ TEST_CASE("Test eventfd signal from ISR", "[vfs][eventfd]") 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 = { + .eventfd_max_num = 5, + }; + 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_ASSERT_EQUAL(0, close(fd)); + 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 = { + .eventfd_max_num = 5, + }; + 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/test/test_vfs_eventfd_integration.c b/components/vfs/test/test_vfs_eventfd_integration.c index 4e59822127..50e19dbafe 100644 --- a/components/vfs/test/test_vfs_eventfd_integration.c +++ b/components/vfs/test/test_vfs_eventfd_integration.c @@ -114,7 +114,10 @@ static void worker_task(void *arg) TEST_CASE("Test eventfd triggered correctly", "[vfs][eventfd]") { xTaskCreate(worker_task, "worker_task", 1024, NULL, 5, NULL); - TEST_ESP_OK(esp_vfs_eventfd_register()); + esp_vfs_eventfd_config_t config = { + .eventfd_max_num = 5, + }; + TEST_ESP_OK(esp_vfs_eventfd_register(&config)); s_timer_fd = eventfd(0, EFD_SUPPORT_ISR); s_progress_fd = eventfd(0, 0); int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd; diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 2e3d6ef3c8..6468232c2c 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,25 +203,13 @@ 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) +esp_err_t esp_vfs_register_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); @@ -211,9 +220,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; @@ -806,21 +819,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; + } } } } @@ -891,6 +906,7 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds } if (is_socket_fd) { + assert(false); if (!socket_select) { // no socket_select found yet so take a look if (esp_vfs_safe_fd_isset(fd, readfds) || diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c index 3aadda17df..808c1ada88 100644 --- a/components/vfs/vfs_eventfd.c +++ b/components/vfs/vfs_eventfd.c @@ -18,33 +18,66 @@ #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 NUM_EVENT_FDS 5 +#define FD_PENDING_SELECT -2 + +typedef struct event_select_args_t { + int fd; + fd_set *read_fds; + fd_set *error_fds; + esp_vfs_select_sem_t signal_sem; + struct event_select_args_t *prev_in_fd; + struct event_select_args_t *next_in_fd; + 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; - fd_set *read_fds; - fd_set *write_fds; - fd_set *error_fds; - esp_vfs_select_sem_t signal_sem; + event_select_args_t *select_args; _lock_t lock; spinlock_t data_spin_lock; // only for event fds that support ISR. -} Event; +} event_context_t; -static Event s_events[NUM_EVENT_FDS]; +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; + } +} static esp_err_t event_start_select(int nfds, fd_set *readfds, @@ -55,42 +88,61 @@ static esp_err_t event_start_select(int nfds, { 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; - for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + // 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); - int fd = s_events[i].fd; - - if (fd != FD_INVALID && fd < nfds) { + if (s_events[i].fd == i) { if (s_events[i].support_isr) { portENTER_CRITICAL(&s_events[i].data_spin_lock); } - s_events[i].signal_sem = signal_sem; - s_events[i].error_fds = exceptfds; - // event fds shouldn't report error - FD_CLR(fd, exceptfds); + 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(fd, writefds)) { - s_events[i].write_fds = writefds; + if (FD_ISSET(i, writefds)) { should_trigger = true; } - if (FD_ISSET(fd, readfds)) { - s_events[i].read_fds = readfds; + if (FD_ISSET(i, readfds)) { + event_select_args->read_fds = readfds; if (s_events[i].is_set) { should_trigger = true; } else { - FD_CLR(fd, readfds); + 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); } @@ -100,61 +152,70 @@ static esp_err_t event_start_select(int nfds, static esp_err_t event_end_select(void *end_select_args) { - for (size_t i = 0; i < NUM_EVENT_FDS; i++) { - _lock_acquire_recursive(&s_events[i].lock); - if (s_events[i].support_isr) { - portENTER_CRITICAL(&s_events[i].data_spin_lock); - } - memset(&s_events[i].signal_sem, 0, sizeof(s_events[i].signal_sem)); - if (s_events[i].read_fds && s_events[i].is_set) { - FD_SET(s_events[i].fd, s_events[i].read_fds); - s_events[i].read_fds = NULL; - } - if (s_events[i].write_fds) { - FD_SET(s_events[i].fd, s_events[i].write_fds); - s_events[i].write_fds = NULL; + 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 (s_events[i].support_isr) { - portEXIT_CRITICAL(&s_events[i].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); + } } - _lock_release_recursive(&s_events[i].lock); + + 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; } -static int event_open(const char *path, int flags, int mode) -{ - - (void)flags; - (void)mode; - - if (path == NULL || path[0] != '/') { - return -1; - } - - char *endPath; - int fd = strtol(path + 1, &endPath, 10); - - if (endPath == NULL || *endPath != '\0' || fd >= NUM_EVENT_FDS) { - return -1; - } - - return fd; -} - - 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); - s_events[fd].is_set = true; - s_events[fd].value += *val; - if (s_events[fd].signal_sem.sem != NULL) { - esp_vfs_select_triggered_isr(s_events[fd].signal_sem, &task_woken); + 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); @@ -162,14 +223,14 @@ static ssize_t signal_event_fd_from_isr(int fd, const void *data, size_t size) if (task_woken) { portYIELD_FROM_ISR(); } - return size; + return ret; } static ssize_t event_write(int fd, const void *data, size_t size) { ssize_t ret = -1; - if (fd >= NUM_EVENT_FDS || data == NULL || size != sizeof(uint64_t)) { + if (fd >= s_event_size || data == NULL || size != sizeof(uint64_t)) { errno = EINVAL; return ret; } @@ -182,21 +243,25 @@ static ssize_t event_write(int fd, const void *data, size_t size) 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); + _lock_acquire_recursive(&s_events[fd].lock); if (s_events[fd].support_isr) { portENTER_CRITICAL(&s_events[fd].data_spin_lock); } - s_events[fd].is_set = true; - s_events[fd].value += *val; - ret = size; - if (s_events[fd].signal_sem.sem != NULL) { - esp_vfs_select_triggered(s_events[fd].signal_sem); - } - if (s_events[fd].support_isr) { - portEXIT_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; @@ -206,32 +271,33 @@ static ssize_t event_read(int fd, void *data, size_t size) { ssize_t ret = -1; - if (fd >= NUM_EVENT_FDS) { - errno = EINVAL; - return ret; - } - if (size != sizeof(uint64_t)) { + 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) { - uint64_t *val = (uint64_t *)data; - if (s_events[fd].support_isr) { - portENTER_CRITICAL(&s_events[fd].data_spin_lock); - } *val = s_events[fd].value; s_events[fd].is_set = false; ret = size; s_events[fd].value = 0; - if (s_events[fd].support_isr) { - portEXIT_CRITICAL(&s_events[fd].data_spin_lock); - } + } 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; } @@ -239,40 +305,55 @@ static int event_close(int fd) { int ret = -1; - if (fd >= NUM_EVENT_FDS) { + 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); } - s_events[fd].fd = FD_INVALID; - memset(&s_events[fd].signal_sem, 0, sizeof(s_events[fd].signal_sem)); + 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); - _lock_close_recursive(&s_events[fd].lock); + return ret; } -esp_err_t esp_vfs_eventfd_register(void) +esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config) { - for (size_t i = 0; i < NUM_EVENT_FDS; i++) { + if (config == NULL || config->eventfd_max_num >= MAX_FDS) { + return ESP_ERR_INVALID_ARG; + } + if (s_eventfd_vfs_id != -1) { + return ESP_ERR_INVALID_STATE; + } + + s_event_size = config->eventfd_max_num; + 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, - .open = &event_open, + .open = NULL, .fstat = NULL, .close = &event_close, .read = &event_read, @@ -288,30 +369,52 @@ esp_err_t esp_vfs_eventfd_register(void) .tcflush = NULL, #endif // CONFIG_SUPPORT_TERMIOS }; - return esp_vfs_register(EVENT_VFS_PREFIX, &vfs, NULL); + return esp_vfs_register_with_id(&vfs, NULL, &s_eventfd_vfs_id); } esp_err_t esp_vfs_eventfd_unregister(void) { - return esp_vfs_unregister(EVENT_VFS_PREFIX); + 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 < NUM_EVENT_FDS; i++) { - bool support_isr = flags & EFD_SUPPORT_ISR; - bool has_allocated = false; - - _lock_init_recursive(&s_events[i].lock); + 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(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); @@ -321,21 +424,30 @@ int eventfd(unsigned int initval, int flags) } s_events[i].is_set = false; s_events[i].value = initval; - memset(&s_events[i].signal_sem, 0, sizeof(s_events[i].signal_sem)); + s_events[i].select_args = NULL; if (support_isr) { portEXIT_CRITICAL(&s_events[i].data_spin_lock); } - - char fullpath[20]; - snprintf(fullpath, sizeof(fullpath), EVENT_VFS_PREFIX "/%d", s_events[i].fd); - fd = open(fullpath, 0, 0); - has_allocated = true; + _lock_release_recursive(&s_events[i].lock); + break; } _lock_release_recursive(&s_events[i].lock); - - if (has_allocated) { - return fd; - } } - return FD_INVALID; + + 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; } From 37a992bbdee53555caaa7d49981aaba0553d03fd Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 15:48:30 +0800 Subject: [PATCH 4/8] vfs: add example for eventfd --- .../vfs/test/test_vfs_eventfd_integration.c | 186 ------------------ examples/system/eventfd/CMakeLists.txt | 6 + examples/system/eventfd/Makefile | 8 + examples/system/eventfd/README.md | 9 + examples/system/eventfd/main/CMakeLists.txt | 2 + examples/system/eventfd/main/component.mk | 4 + .../system/eventfd/main/eventfd_example.c | 168 ++++++++++++++++ 7 files changed, 197 insertions(+), 186 deletions(-) delete mode 100644 components/vfs/test/test_vfs_eventfd_integration.c create mode 100644 examples/system/eventfd/CMakeLists.txt create mode 100644 examples/system/eventfd/Makefile create mode 100644 examples/system/eventfd/README.md create mode 100644 examples/system/eventfd/main/CMakeLists.txt create mode 100644 examples/system/eventfd/main/component.mk create mode 100644 examples/system/eventfd/main/eventfd_example.c diff --git a/components/vfs/test/test_vfs_eventfd_integration.c b/components/vfs/test/test_vfs_eventfd_integration.c deleted file mode 100644 index 50e19dbafe..0000000000 --- a/components/vfs/test/test_vfs_eventfd_integration.c +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License - -#include "esp_vfs_eventfd.h" - -#include -#include -#include - -#include "driver/periph_ctrl.h" -#include "driver/timer.h" -#include "esp_err.h" -#include "esp_types.h" -#include "esp_vfs.h" -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -#include "freertos/task.h" -#include "hal/timer_types.h" -#include "unity.h" - -#define TIMER_DIVIDER 16 -#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) -#define TIMER_INTERVAL0_SEC (0.25) -#define TEST_WITHOUT_RELOAD 0 -#define PROGRESS_INTERVAL_MS 350 -#define TIMER_SIGNAL 1 -#define PROGRESS_SIGNAL 2 - -int s_timer_fd; -int s_progress_fd; - -/* - * A simple helper function to print the raw timer counter value - * and the counter value converted to seconds - */ -static void inline print_timer_counter(uint64_t counter_value) -{ - printf("Counter: 0x%08x%08x\n", (uint32_t) (counter_value >> 32), - (uint32_t) (counter_value)); - printf("Time : %.8f s\n", (double) counter_value / TIMER_SCALE); -} - -static void IRAM_ATTR 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_INTERVAL0_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, - }; - TEST_ESP_OK(timer_init(TIMER_GROUP_0, timer_idx, &config)); - - TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL)); - - TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE)); - TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, timer_idx)); - TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, timer_idx, eventfd_timer_group0_isr, - (void *) timer_idx, ESP_INTR_FLAG_IRAM, NULL)); - - TEST_ESP_OK(timer_start(TIMER_GROUP_0, timer_idx)); -} - -static void eventfd_timer_deinit(int timer_idx) -{ - timer_pause(TIMER_GROUP_0, timer_idx); - timer_deinit(TIMER_GROUP_0, timer_idx); -} - -static void worker_task(void *arg) -{ - for (int i = 0; i < 3; i++) { - 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); -} - -TEST_CASE("Test eventfd triggered correctly", "[vfs][eventfd]") -{ - xTaskCreate(worker_task, "worker_task", 1024, NULL, 5, NULL); - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; - TEST_ESP_OK(esp_vfs_eventfd_register(&config)); - s_timer_fd = eventfd(0, EFD_SUPPORT_ISR); - s_progress_fd = eventfd(0, 0); - int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd; - printf("Timer fd %d progress fd %d\n", s_timer_fd, s_progress_fd); - TEST_ASSERT_GREATER_OR_EQUAL(0, s_timer_fd); - TEST_ASSERT_GREATER_OR_EQUAL(0, s_progress_fd); - eventfd_timer_init(TIMER_0, TIMER_INTERVAL0_SEC); - - int select_timeout_count = 0; - int timer_trigger_count = 0; - int progress_trigger_count = 0; - - for (size_t i = 0; i < 10; i++) { - struct timeval timeout; - uint64_t signal; - - timeout.tv_sec = 0; - timeout.tv_usec = 200 * 1000; - - fd_set readfds; - fd_set writefds; - fd_set errorfds; - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&errorfds); - - FD_SET(s_timer_fd, &readfds); - FD_SET(s_progress_fd, &readfds); - - select(maxFd + 1, &readfds, &writefds, &errorfds, &timeout); - - printf("-------- TASK TIME --------\n"); - uint64_t task_counter_value; - TEST_ESP_OK(timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value)); - print_timer_counter(task_counter_value); - - if (FD_ISSET(s_progress_fd, &readfds)) { - ssize_t ret = read(s_progress_fd, &signal, sizeof(signal)); - TEST_ASSERT_EQUAL(ret, sizeof(signal)); - TEST_ASSERT_EQUAL(signal, PROGRESS_SIGNAL); - progress_trigger_count++; - printf("Progress fd\n"); - } else if (FD_ISSET(s_timer_fd, &readfds)) { - ssize_t ret = read(s_timer_fd, &signal, sizeof(signal)); - TEST_ASSERT(ret == sizeof(signal)); - TEST_ASSERT(signal == TIMER_SIGNAL); - timer_trigger_count++; - printf("TimerEvent fd\n"); - } else { - select_timeout_count++; - printf("Select timeout\n"); - } - } - - printf("Select timeout: %d\n", select_timeout_count); - printf("Timer trigger: %d\n", timer_trigger_count); - printf("Progress trigger: %d\n", progress_trigger_count); - TEST_ASSERT_EQUAL(3, select_timeout_count); - TEST_ASSERT_EQUAL(4, timer_trigger_count); - TEST_ASSERT_EQUAL(3, progress_trigger_count); - TEST_ASSERT_EQUAL(0, close(s_progress_fd)); - TEST_ASSERT_EQUAL(0, close(s_timer_fd)); - eventfd_timer_deinit(TIMER_0); - TEST_ESP_OK(esp_vfs_eventfd_unregister()); -} 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..e70931d40b --- /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 := select + +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..9bcfc0acaa --- /dev/null +++ b/examples/system/eventfd/README.md @@ -0,0 +1,9 @@ +# 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. + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/system/eventfd/main/CMakeLists.txt b/examples/system/eventfd/main/CMakeLists.txt new file mode 100644 index 0000000000..52e676a338 --- /dev/null +++ b/examples/system/eventfd/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "eventfd_example.c" + INCLUDE_DIRS ".") diff --git a/examples/system/eventfd/main/component.mk b/examples/system/eventfd/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/system/eventfd/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/system/eventfd/main/eventfd_example.c b/examples/system/eventfd/main/eventfd_example.c new file mode 100644 index 0000000000..67e7745436 --- /dev/null +++ b/examples/system/eventfd/main/eventfd_example.c @@ -0,0 +1,168 @@ +/* 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 IRAM_ATTR 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 = { + .eventfd_max_num = 2, + }; + 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); +} From 7c64d1530acb8c66a3193440f407d06fa9f9faa9 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 15:49:22 +0800 Subject: [PATCH 5/8] vfs: modify copyright header --- components/vfs/test/test_vfs_eventfd.c | 2 +- components/vfs/vfs_eventfd.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c index 5d7732b540..9bba81402e 100644 --- a/components/vfs/test/test_vfs_eventfd.c +++ b/components/vfs/test/test_vfs_eventfd.c @@ -1,4 +1,4 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// 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. diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c index 808c1ada88..635c5f048c 100644 --- a/components/vfs/vfs_eventfd.c +++ b/components/vfs/vfs_eventfd.c @@ -1,4 +1,4 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// 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. From 40035edcac9b97a6827bb6bf7c30d0b6a54b7844 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 16:29:22 +0800 Subject: [PATCH 6/8] vfs: add doc links --- components/vfs/README.rst | 11 +++++++++++ docs/doxygen/Doxyfile_common | 1 + docs/en/api-reference/storage/vfs.rst | 1 + 3 files changed, 13 insertions(+) diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 6a4dce7990..9c80bffdf9 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()`` +- Option ``EFD_CLOEXEC``, ``EFD_NONBLOCK`` and ``EFD_SEMAPHORE`` is 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/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 From 0d986de36326f792271855670bba17249af756f8 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 16:58:11 +0800 Subject: [PATCH 7/8] vfs: add default initializer for vfs_eventfd --- components/vfs/include/esp_vfs_eventfd.h | 6 ++- components/vfs/test/test_vfs_eventfd.c | 47 +++++-------------- components/vfs/vfs_eventfd.c | 14 ++---- .../system/eventfd/main/eventfd_example.c | 4 +- 4 files changed, 21 insertions(+), 50 deletions(-) diff --git a/components/vfs/include/esp_vfs_eventfd.h b/components/vfs/include/esp_vfs_eventfd.h index 90c7b5939b..bae1a93f13 100644 --- a/components/vfs/include/esp_vfs_eventfd.h +++ b/components/vfs/include/esp_vfs_eventfd.h @@ -26,9 +26,13 @@ extern "C" { #endif typedef struct { - size_t eventfd_max_num; + size_t max_fds; } esp_vfs_eventfd_config_t; +#define ESP_VFS_EVENTD_CONFIG_DEFAULT() (esp_vfs_eventfd_config_t) { \ + .max_fds = 5, \ +}; + /** * @brief Registers the event vfs. * diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c index 9bba81402e..f6500955ea 100644 --- a/components/vfs/test/test_vfs_eventfd.c +++ b/components/vfs/test/test_vfs_eventfd.c @@ -24,9 +24,7 @@ TEST_CASE("eventfd create and close", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + 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); @@ -40,9 +38,7 @@ TEST_CASE("eventfd create and close", "[vfs][eventfd]") TEST_CASE("eventfd reject unknown flags", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 1); @@ -57,9 +53,7 @@ TEST_CASE("eventfd reject unknown flags", "[vfs][eventfd]") TEST_CASE("eventfd read", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); unsigned int initval = 123; @@ -77,9 +71,7 @@ TEST_CASE("eventfd read", "[vfs][eventfd]") TEST_CASE("eventfd read invalid size", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -94,9 +86,7 @@ TEST_CASE("eventfd read invalid size", "[vfs][eventfd]") TEST_CASE("eventfd write invalid size", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -111,9 +101,7 @@ TEST_CASE("eventfd write invalid size", "[vfs][eventfd]") TEST_CASE("eventfd write then read", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -135,9 +123,7 @@ TEST_CASE("eventfd write then read", "[vfs][eventfd]") TEST_CASE("eventfd instant select", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -153,7 +139,6 @@ TEST_CASE("eventfd instant select", "[vfs][eventfd]") FD_ZERO(&write_fds); FD_ZERO(&error_fds); FD_SET(fd, &read_fds); - printf("fd %d\n", fd); 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)); @@ -191,9 +176,7 @@ static void signal_task(void *arg) TEST_CASE("eventfd signal from task", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd0 = eventfd(0, 0); @@ -215,7 +198,6 @@ TEST_CASE("eventfd signal from task", "[vfs][eventfd]") zero_time.tv_usec = 0; int ret = select(max_fd + 1, &read_fds, NULL, NULL, &wait_time); - printf("Frist select returned\n"); TEST_ASSERT_EQUAL(1, ret); TEST_ASSERT(FD_ISSET(fd0, &read_fds)); @@ -246,9 +228,7 @@ static void IRAM_ATTR eventfd_select_test_isr(void *arg) TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + 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); @@ -299,9 +279,7 @@ static void close_task(void *arg) TEST_CASE("eventfd select closed fd", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -350,9 +328,7 @@ static void select_task(void *arg) TEST_CASE("eventfd multiple selects", "[vfs][eventfd]") { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 5, - }; + esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); TEST_ESP_OK(esp_vfs_eventfd_register(&config)); int fd = eventfd(0, 0); @@ -380,4 +356,3 @@ TEST_CASE("eventfd multiple selects", "[vfs][eventfd]") TEST_ASSERT_EQUAL(0, close(fd)); TEST_ESP_OK(esp_vfs_eventfd_unregister()); } - diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c index 635c5f048c..a32833b6e3 100644 --- a/components/vfs/vfs_eventfd.c +++ b/components/vfs/vfs_eventfd.c @@ -41,7 +41,7 @@ typedef struct event_select_args_t { esp_vfs_select_sem_t signal_sem; struct event_select_args_t *prev_in_fd; struct event_select_args_t *next_in_fd; - struct event_select_args_t *next_in_args; + struct event_select_args_t *next_in_args; // a linked list for all pending select args for one select call } event_select_args_t; typedef struct { @@ -49,7 +49,7 @@ typedef struct { bool support_isr; volatile bool is_set; volatile uint64_t value; - event_select_args_t *select_args; + event_select_args_t *select_args; // a double-linked list for all pending select args with this fd _lock_t lock; spinlock_t data_spin_lock; // only for event fds that support ISR. } event_context_t; @@ -336,14 +336,14 @@ static int event_close(int fd) esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config) { - if (config == NULL || config->eventfd_max_num >= MAX_FDS) { + 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->eventfd_max_num; + 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); @@ -362,12 +362,6 @@ esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config) .access = NULL, .start_select = &event_start_select, .end_select = &event_end_select, -#ifdef CONFIG_SUPPORT_TERMIOS - .tcsetattr = NULL, - .tcgetattr = NULL, - .tcdrain = NULL, - .tcflush = NULL, -#endif // CONFIG_SUPPORT_TERMIOS }; return esp_vfs_register_with_id(&vfs, NULL, &s_eventfd_vfs_id); } diff --git a/examples/system/eventfd/main/eventfd_example.c b/examples/system/eventfd/main/eventfd_example.c index 67e7745436..fb0f0ecdbd 100644 --- a/examples/system/eventfd/main/eventfd_example.c +++ b/examples/system/eventfd/main/eventfd_example.c @@ -90,9 +90,7 @@ static void worker_task(void *arg) static void collector_task(void *arg) { - esp_vfs_eventfd_config_t config = { - .eventfd_max_num = 2, - }; + 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); From bbb0e7a5739c1cb5fcc7212fefe09281f6f27089 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 8 Mar 2021 19:52:05 +0800 Subject: [PATCH 8/8] vfs: fix build & ci errors --- .gitlab/ci/target-test.yml | 2 +- .../bluedroid/btc/profile/std/spp/btc_spp.c | 2 +- components/vfs/README.rst | 2 +- components/vfs/include/esp_vfs.h | 19 +++++- components/vfs/include/esp_vfs_eventfd.h | 7 +- components/vfs/test/test_vfs_eventfd.c | 5 +- components/vfs/vfs.c | 14 ++-- components/vfs/vfs_eventfd.c | 38 ++++++++--- examples/system/eventfd/Makefile | 2 +- examples/system/eventfd/README.md | 67 ++++++++++++++++++- examples/system/eventfd/example_test.py | 29 ++++++++ examples/system/eventfd/main/CMakeLists.txt | 1 + examples/system/eventfd/main/component.mk | 2 + .../system/eventfd/main/eventfd_example.c | 2 +- examples/system/eventfd/main/linker.lf | 4 ++ 15 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 examples/system/eventfd/example_test.py create mode 100644 examples/system/eventfd/main/linker.lf diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index efeb0a91a1..acd48844cd 100644 --- a/.gitlab/ci/target-test.yml +++ b/.gitlab/ci/target-test.yml @@ -410,7 +410,7 @@ UT_001: UT_002: extends: .unit_test_esp32_template - parallel: 13 + parallel: 15 tags: - ESP32_IDF - UT_T1_1 diff --git a/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c b/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c index 1608892d01..a8fe25e352 100644 --- a/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c +++ b/components/bt/host/bluedroid/btc/profile/std/spp/btc_spp.c @@ -151,7 +151,7 @@ static spp_slot_t *spp_malloc_slot(void) break; } if (spp_local_param.spp_mode == ESP_SPP_MODE_VFS) { - if (esp_vfs_register_fd(spp_local_param.spp_vfs_id, -1, /*permanent=*/true, &(*slot)->fd) != ESP_OK) { + if (esp_vfs_register_fd(spp_local_param.spp_vfs_id, &(*slot)->fd) != ESP_OK) { BTC_TRACE_ERROR("%s unable to register fd!", __func__); err_no = 3; break; diff --git a/components/vfs/README.rst b/components/vfs/README.rst index 9c80bffdf9..4765f14433 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -242,7 +242,7 @@ 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()`` -- Option ``EFD_CLOEXEC``, ``EFD_NONBLOCK`` and ``EFD_SEMAPHORE`` is not supported in flags. +- 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 30689d6d89..7ba6364530 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -339,15 +339,28 @@ esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id); * 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 permanenent 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(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd); +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 diff --git a/components/vfs/include/esp_vfs_eventfd.h b/components/vfs/include/esp_vfs_eventfd.h index bae1a93f13..430112111a 100644 --- a/components/vfs/include/esp_vfs_eventfd.h +++ b/components/vfs/include/esp_vfs_eventfd.h @@ -1,4 +1,4 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -25,8 +25,11 @@ extern "C" { #endif +/** + * @brief Eventfd vfs initialization settings + */ typedef struct { - size_t max_fds; + 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) { \ diff --git a/components/vfs/test/test_vfs_eventfd.c b/components/vfs/test/test_vfs_eventfd.c index f6500955ea..0d051008f1 100644 --- a/components/vfs/test/test_vfs_eventfd.c +++ b/components/vfs/test/test_vfs_eventfd.c @@ -217,7 +217,7 @@ TEST_CASE("eventfd signal from task", "[vfs][eventfd]") TEST_ESP_OK(esp_vfs_eventfd_unregister()); } -static void IRAM_ATTR eventfd_select_test_isr(void *arg) +static void eventfd_select_test_isr(void *arg) { int fd = *((int *)arg); uint64_t val = 1; @@ -248,7 +248,7 @@ TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]") 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_IRAM, NULL)); + &fd, ESP_INTR_FLAG_LOWMED, NULL)); TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0)); struct timeval wait_time; @@ -300,7 +300,6 @@ TEST_CASE("eventfd select closed fd", "[vfs][eventfd]") TEST_ASSERT_EQUAL(1, ret); TEST_ASSERT(FD_ISSET(fd, &error_fds)); - 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 6468232c2c..46890e3e64 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -209,10 +209,16 @@ esp_err_t esp_vfs_unregister(const char* base_path) return ESP_ERR_INVALID_STATE; } -esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd) +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; } @@ -234,7 +240,8 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, } _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; } @@ -906,7 +913,6 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds } if (is_socket_fd) { - assert(false); if (!socket_select) { // no socket_select found yet so take a look if (esp_vfs_safe_fd_isset(fd, readfds) || diff --git a/components/vfs/vfs_eventfd.c b/components/vfs/vfs_eventfd.c index a32833b6e3..f5f3c49ed5 100644 --- a/components/vfs/vfs_eventfd.c +++ b/components/vfs/vfs_eventfd.c @@ -34,14 +34,30 @@ #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; - struct event_select_args_t *next_in_args; // a linked list for all pending select args for one select call + // linked list node in end_select_arg + struct event_select_args_t *next_in_args; } event_select_args_t; typedef struct { @@ -49,9 +65,11 @@ typedef struct { bool support_isr; volatile bool is_set; volatile uint64_t value; - event_select_args_t *select_args; // a double-linked list for all pending select args with this fd + // a double-linked list for all pending select args with this fd + event_select_args_t *select_args; _lock_t lock; - spinlock_t data_spin_lock; // only for event fds that support ISR. + // only for event fds that support ISR. + spinlock_t data_spin_lock; } event_context_t; esp_vfs_id_t s_eventfd_vfs_id = -1; @@ -79,6 +97,7 @@ static void trigger_select_for_event_isr(event_context_t *event, BaseType_t *tas } } +#ifdef CONFIG_VFS_SUPPORT_SELECT static esp_err_t event_start_select(int nfds, fd_set *readfds, fd_set *writefds, @@ -100,7 +119,8 @@ static esp_err_t event_start_select(int nfds, 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_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; @@ -200,6 +220,7 @@ static esp_err_t event_end_select(void *end_select_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) { @@ -353,15 +374,12 @@ esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config) esp_vfs_t vfs = { .flags = ESP_VFS_FLAG_DEFAULT, .write = &event_write, - .open = NULL, - .fstat = NULL, .close = &event_close, .read = &event_read, - .fcntl = NULL, - .fsync = NULL, - .access = NULL, +#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); } @@ -401,7 +419,7 @@ int eventfd(unsigned int initval, int flags) _lock_acquire_recursive(&s_events[i].lock); if (s_events[i].fd == FD_INVALID) { - error = esp_vfs_register_fd(s_eventfd_vfs_id, i, /*permanent=*/false, &global_fd); + 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; diff --git a/examples/system/eventfd/Makefile b/examples/system/eventfd/Makefile index e70931d40b..c26394d601 100644 --- a/examples/system/eventfd/Makefile +++ b/examples/system/eventfd/Makefile @@ -3,6 +3,6 @@ # project subdirectory. # -PROJECT_NAME := select +PROJECT_NAME := eventfd include $(IDF_PATH)/make/project.mk diff --git a/examples/system/eventfd/README.md b/examples/system/eventfd/README.md index 9bcfc0acaa..2cbb444295 100644 --- a/examples/system/eventfd/README.md +++ b/examples/system/eventfd/README.md @@ -6,4 +6,69 @@ The example demonstrates the use of `eventfd()` to collect events from other tas 2. The timer interrupt handler writes to the second `eventfd`. 3. The second task collects the event from two fds with a `select()` loop. -See the README.md file in the upper level 'examples' directory for more information about examples. +## 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 index 52e676a338..dafbf41a79 100644 --- a/examples/system/eventfd/main/CMakeLists.txt +++ b/examples/system/eventfd/main/CMakeLists.txt @@ -1,2 +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 index a98f634eae..f3cf3c2887 100644 --- a/examples/system/eventfd/main/component.mk +++ b/examples/system/eventfd/main/component.mk @@ -2,3 +2,5 @@ # "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 index fb0f0ecdbd..727c145a7b 100644 --- a/examples/system/eventfd/main/eventfd_example.c +++ b/examples/system/eventfd/main/eventfd_example.c @@ -34,7 +34,7 @@ static const char *TAG = "eventfd_example"; int s_timer_fd; int s_progress_fd; -static void IRAM_ATTR eventfd_timer_group0_isr(void *para) +static void eventfd_timer_group0_isr(void *para) { timer_spinlock_take(TIMER_GROUP_0); int timer_idx = (int) para; 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)