From f6fd2eb1648ece6139c9995fcb46d25843b57cdd Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 1 Mar 2021 17:51:48 +0800 Subject: [PATCH] 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; +}