forked from espressif/esp-idf
Merge branch 'feature/add-eventfd' into 'master'
vfs: add eventfd support See merge request espressif/esp-idf!12573
This commit is contained in:
@@ -411,7 +411,7 @@ UT_001:
|
||||
|
||||
UT_002:
|
||||
extends: .unit_test_esp32_template
|
||||
parallel: 13
|
||||
parallel: 15
|
||||
tags:
|
||||
- ESP32_IDF
|
||||
- UT_T1_1
|
||||
|
@@ -1,4 +1,5 @@
|
||||
idf_component_register(SRCS "vfs.c"
|
||||
"vfs_eventfd.c"
|
||||
"vfs_uart.c"
|
||||
"vfs_semihost.c"
|
||||
INCLUDE_DIRS include)
|
||||
|
@@ -235,3 +235,14 @@ Such a design has the following consequences:
|
||||
- It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g., by doing ``stdin = fopen("/dev/uart/1", "r")``.
|
||||
- Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object, which will affect all other tasks.
|
||||
- To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task.
|
||||
|
||||
Event fds
|
||||
-------------------------------------------
|
||||
|
||||
``eventfd()`` call is a powerful tool to notify a ``select()`` based loop of custom events. The ``eventfd()`` implementation in ESP-IDF is generally the same as described in ``man(2) eventfd`` except for:
|
||||
|
||||
- ``esp_vfs_eventfd_register()`` has to be called before calling ``eventfd()``
|
||||
- Options ``EFD_CLOEXEC``, ``EFD_NONBLOCK`` and ``EFD_SEMAPHORE`` are not supported in flags.
|
||||
- Option ``EFD_SUPPORT_ISR`` has been added in flags. This flag is required to read and the write the eventfd in an interrupt handler.
|
||||
|
||||
Note that creating an eventfd with ``EFD_SUPPORT_ISR`` will cause interrupts to be temporarily disabled when reading, writing the file and during the beginning and the ending of the ``select()`` when this file is set.
|
||||
|
@@ -325,6 +325,15 @@ esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t
|
||||
*/
|
||||
esp_err_t esp_vfs_unregister(const char* base_path);
|
||||
|
||||
/**
|
||||
* Unregister a virtual filesystem with the given index
|
||||
*
|
||||
* @param vfs_id The VFS ID returned by esp_vfs_register_with_id
|
||||
* @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for the given index
|
||||
* hasn't been registered
|
||||
*/
|
||||
esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id);
|
||||
|
||||
/**
|
||||
* Special function for registering another file descriptor for a VFS registered
|
||||
* by esp_vfs_register_with_id.
|
||||
@@ -338,6 +347,21 @@ esp_err_t esp_vfs_unregister(const char* base_path);
|
||||
*/
|
||||
esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd);
|
||||
|
||||
/**
|
||||
* Special function for registering another file descriptor with given local_fd
|
||||
* for a VFS registered by esp_vfs_register_with_id.
|
||||
*
|
||||
* @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
|
||||
* @param local_fd The fd in the local vfs. Passing -1 will set the local fd as the (*fd) value.
|
||||
* @param permanent Whether the fd should be treated as permannet (not removed after close())
|
||||
* @param fd The registered file descriptor will be written to this address.
|
||||
*
|
||||
* @return ESP_OK if the registration is successful,
|
||||
* ESP_ERR_NO_MEM if too many file descriptors are registered,
|
||||
* ESP_ERR_INVALID_ARG if the arguments are incorrect.
|
||||
*/
|
||||
esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd);
|
||||
|
||||
/**
|
||||
* Special function for unregistering a file descriptor belonging to a VFS
|
||||
* registered by esp_vfs_register_with_id.
|
||||
|
71
components/vfs/include/esp_vfs_eventfd.h
Normal file
71
components/vfs/include/esp_vfs_eventfd.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#define EFD_SUPPORT_ISR (1 << 4)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Eventfd vfs initialization settings
|
||||
*/
|
||||
typedef struct {
|
||||
size_t max_fds; /*!< The maxinum number of eventfds supported */
|
||||
} esp_vfs_eventfd_config_t;
|
||||
|
||||
#define ESP_VFS_EVENTD_CONFIG_DEFAULT() (esp_vfs_eventfd_config_t) { \
|
||||
.max_fds = 5, \
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Registers the event vfs.
|
||||
*
|
||||
* @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
|
||||
* registered.
|
||||
*/
|
||||
esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Unregisters the event vfs.
|
||||
*
|
||||
* @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix
|
||||
* hasn't been registered
|
||||
*/
|
||||
esp_err_t esp_vfs_eventfd_unregister(void);
|
||||
|
||||
/*
|
||||
* @brief Creates an event file descirptor.
|
||||
*
|
||||
* The behavior of read, write and select is the same as man(2) eventfd with
|
||||
* EFD_SEMAPHORE **NOT** specified. A new flag EFD_SUPPORT_ISR has been added.
|
||||
* This flag is required to write to event fds in interrupt handlers. Accessing
|
||||
* the control blocks of event fds with EFD_SUPPORT_ISR will cause interrupts to
|
||||
* be temporarily blocked (e.g. during read, write and beginning and ending of
|
||||
* the * select).
|
||||
*
|
||||
* @return The file descriptor if successful, -1 if error happens.
|
||||
*/
|
||||
int eventfd(unsigned int initval, int flags);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
357
components/vfs/test/test_vfs_eventfd.c
Normal file
357
components/vfs/test/test_vfs_eventfd.c
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
#include "esp_vfs_eventfd.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "driver/timer.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "unity.h"
|
||||
|
||||
TEST_CASE("eventfd create and close", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
|
||||
fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd reject unknown flags", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 1);
|
||||
TEST_ASSERT_LESS_THAN(0, fd);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
|
||||
fd = eventfd(0, INT_MAX);
|
||||
TEST_ASSERT_LESS_THAN(0, fd);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd read", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
unsigned int initval = 123;
|
||||
int fd = eventfd(initval, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
uint64_t val = 0;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(initval, val);
|
||||
TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(0, val);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd read invalid size", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
uint32_t val = 0;
|
||||
TEST_ASSERT_LESS_THAN(0, read(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd write invalid size", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
uint32_t val = 0;
|
||||
TEST_ASSERT_LESS_THAN(0, write(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd write then read", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
uint64_t val = 123;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(123, val);
|
||||
val = 4;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
val = 5;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
|
||||
TEST_ASSERT_EQUAL(9, val);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd instant select", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
struct timeval zero_time;
|
||||
fd_set read_fds, write_fds, error_fds;
|
||||
|
||||
zero_time.tv_sec = 0;
|
||||
zero_time.tv_usec = 0;
|
||||
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
|
||||
TEST_ASSERT_EQUAL(0, ret);
|
||||
TEST_ASSERT(!FD_ISSET(fd, &read_fds));
|
||||
|
||||
uint64_t val = 1;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
|
||||
TEST_ASSERT_EQUAL(1, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd, &read_fds));
|
||||
|
||||
TEST_ASSERT_EQUAL(sizeof(val), read(fd, &val, sizeof(val)));
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &zero_time);
|
||||
TEST_ASSERT_EQUAL(0, ret);
|
||||
TEST_ASSERT(!FD_ISSET(fd, &read_fds));
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
static void signal_task(void *arg)
|
||||
{
|
||||
int fd = *((int *)arg);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
uint64_t val = 1;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd signal from task", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd0 = eventfd(0, 0);
|
||||
int fd1 = eventfd(0, 0);
|
||||
int max_fd = fd1 > fd0 ? fd1 : fd0;
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd1);
|
||||
|
||||
xTaskCreate(signal_task, "signal_task", 2048, &fd0, 5, NULL);
|
||||
struct timeval wait_time;
|
||||
struct timeval zero_time;
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd0, &read_fds);
|
||||
FD_SET(fd1, &read_fds);
|
||||
wait_time.tv_sec = 2;
|
||||
wait_time.tv_usec = 0;
|
||||
zero_time.tv_sec = 0;
|
||||
zero_time.tv_usec = 0;
|
||||
|
||||
int ret = select(max_fd + 1, &read_fds, NULL, NULL, &wait_time);
|
||||
TEST_ASSERT_EQUAL(1, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd0, &read_fds));
|
||||
|
||||
uint64_t val = 1;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd1, &val, sizeof(val)));
|
||||
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd0, &read_fds);
|
||||
FD_SET(fd1, &read_fds);
|
||||
ret = select(max_fd + 1, &read_fds, NULL, NULL, &zero_time);
|
||||
TEST_ASSERT_EQUAL(2, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd0, &read_fds));
|
||||
TEST_ASSERT(FD_ISSET(fd1, &read_fds));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd0));
|
||||
TEST_ASSERT_EQUAL(0, close(fd1));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
static void eventfd_select_test_isr(void *arg)
|
||||
{
|
||||
int fd = *((int *)arg);
|
||||
uint64_t val = 1;
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
int ret = write(fd, &val, sizeof(val));
|
||||
assert(ret == sizeof(val));
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
timer_config_t timer_config = {
|
||||
.divider = 16,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.auto_reload = false,
|
||||
};
|
||||
TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &timer_config));
|
||||
|
||||
TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL));
|
||||
|
||||
TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_BASE_CLK / 16));
|
||||
TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
|
||||
TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, TIMER_0, eventfd_select_test_isr,
|
||||
&fd, ESP_INTR_FLAG_LOWMED, NULL));
|
||||
TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0));
|
||||
|
||||
struct timeval wait_time;
|
||||
fd_set read_fds, write_fds, error_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
wait_time.tv_sec = 2;
|
||||
wait_time.tv_usec = 0;
|
||||
|
||||
FD_SET(fd, &read_fds);
|
||||
int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time);
|
||||
TEST_ASSERT_EQUAL(1, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd, &read_fds));
|
||||
timer_deinit(TIMER_GROUP_0, TIMER_0);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
static void close_task(void *arg)
|
||||
{
|
||||
int fd = *((int *)arg);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd select closed fd", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
xTaskCreate(close_task, "close_task", 2048, &fd, 5, NULL);
|
||||
struct timeval wait_time;
|
||||
fd_set read_fds, write_fds, error_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
FD_SET(fd, &error_fds);
|
||||
wait_time.tv_sec = 2;
|
||||
wait_time.tv_usec = 0;
|
||||
|
||||
int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time);
|
||||
TEST_ASSERT_EQUAL(1, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd, &error_fds));
|
||||
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
xQueueHandle queue;
|
||||
int fd;
|
||||
} select_task_args_t;
|
||||
|
||||
static void select_task(void *arg)
|
||||
{
|
||||
select_task_args_t *select_arg = (select_task_args_t *)arg;
|
||||
int fd = select_arg->fd;
|
||||
struct timeval wait_time;
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
wait_time.tv_sec = 2;
|
||||
wait_time.tv_usec = 0;
|
||||
|
||||
int ret = select(fd + 1, &read_fds, NULL, NULL, &wait_time);
|
||||
assert(ret == 1);
|
||||
xQueueSend(select_arg->queue, select_arg, 0);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd multiple selects", "[vfs][eventfd]")
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
int fd = eventfd(0, 0);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
select_task_args_t args = {
|
||||
.queue = xQueueCreate(10, sizeof(select_task_args_t)),
|
||||
.fd = fd,
|
||||
};
|
||||
select_task_args_t ret_args;
|
||||
|
||||
xTaskCreate(select_task, "select_task0", 2048, &args, 5, NULL);
|
||||
xTaskCreate(select_task, "select_task1", 2048, &args, 5, NULL);
|
||||
|
||||
uint64_t val = 1;
|
||||
TEST_ASSERT_EQUAL(sizeof(val), write(fd, &val, sizeof(val)));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
TEST_ASSERT(xQueueReceive(args.queue, &ret_args, 0));
|
||||
TEST_ASSERT_EQUAL(ret_args.fd, fd);
|
||||
TEST_ASSERT(xQueueReceive(args.queue, &ret_args, 0));
|
||||
TEST_ASSERT_EQUAL(ret_args.fd, fd);
|
||||
|
||||
vQueueDelete(args.queue);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
@@ -172,6 +172,27 @@ esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t
|
||||
return esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, vfs_id);
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id)
|
||||
{
|
||||
if (vfs_id < 0 || vfs_id >= MAX_FDS || s_vfs[vfs_id] == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
vfs_entry_t* vfs = s_vfs[vfs_id];
|
||||
free(vfs);
|
||||
s_vfs[vfs_id] = NULL;
|
||||
|
||||
_lock_acquire(&s_fd_table_lock);
|
||||
// Delete all references from the FD lookup-table
|
||||
for (int j = 0; j < VFS_MAX_COUNT; ++j) {
|
||||
if (s_fd_table[j].vfs_index == vfs_id) {
|
||||
s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
|
||||
}
|
||||
}
|
||||
_lock_release(&s_fd_table_lock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_unregister(const char* base_path)
|
||||
{
|
||||
const size_t base_path_len = strlen(base_path);
|
||||
@@ -182,28 +203,22 @@ esp_err_t esp_vfs_unregister(const char* base_path)
|
||||
}
|
||||
if (base_path_len == vfs->path_prefix_len &&
|
||||
memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) {
|
||||
free(vfs);
|
||||
s_vfs[i] = NULL;
|
||||
|
||||
_lock_acquire(&s_fd_table_lock);
|
||||
// Delete all references from the FD lookup-table
|
||||
for (int j = 0; j < MAX_FDS; ++j) {
|
||||
if (s_fd_table[j].vfs_index == i) {
|
||||
s_fd_table[j] = FD_TABLE_ENTRY_UNUSED;
|
||||
}
|
||||
}
|
||||
_lock_release(&s_fd_table_lock);
|
||||
|
||||
return ESP_OK;
|
||||
return esp_vfs_unregister_with_id(i);
|
||||
}
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
|
||||
{
|
||||
return esp_vfs_register_fd_with_local_fd(vfs_id, -1, true, fd);
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd)
|
||||
{
|
||||
if (vfs_id < 0 || vfs_id >= s_vfs_count || fd == NULL) {
|
||||
ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd(%d, 0x%x)", vfs_id, (int) fd);
|
||||
ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd_with_local_fd(%d, %d, %d, 0x%p)",
|
||||
vfs_id, local_fd, permanent, fd);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@@ -211,9 +226,13 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
|
||||
_lock_acquire(&s_fd_table_lock);
|
||||
for (int i = 0; i < MAX_FDS; ++i) {
|
||||
if (s_fd_table[i].vfs_index == -1) {
|
||||
s_fd_table[i].permanent = true;
|
||||
s_fd_table[i].permanent = permanent;
|
||||
s_fd_table[i].vfs_index = vfs_id;
|
||||
s_fd_table[i].local_fd = i;
|
||||
if (local_fd >= 0) {
|
||||
s_fd_table[i].local_fd = local_fd;
|
||||
} else {
|
||||
s_fd_table[i].local_fd = i;
|
||||
}
|
||||
*fd = i;
|
||||
ret = ESP_OK;
|
||||
break;
|
||||
@@ -221,7 +240,8 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)
|
||||
}
|
||||
_lock_release(&s_fd_table_lock);
|
||||
|
||||
ESP_LOGD(TAG, "esp_vfs_register_fd(%d, 0x%x) finished with %s", vfs_id, (int) fd, esp_err_to_name(ret));
|
||||
ESP_LOGD(TAG, "esp_vfs_register_fd_with_local_fd(%d, %d, %d, 0x%p) finished with %s",
|
||||
vfs_id, local_fd, permanent, fd, esp_err_to_name(ret));
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -806,21 +826,23 @@ static int set_global_fd_sets(const fds_triple_t *vfs_fds_triple, int size, fd_s
|
||||
const fds_triple_t *item = &vfs_fds_triple[i];
|
||||
if (item->isset) {
|
||||
for (int fd = 0; fd < MAX_FDS; ++fd) {
|
||||
const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
|
||||
if (readfds && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) {
|
||||
ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, readfds);
|
||||
++ret;
|
||||
}
|
||||
if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) {
|
||||
ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, writefds);
|
||||
++ret;
|
||||
}
|
||||
if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) {
|
||||
ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, errorfds);
|
||||
++ret;
|
||||
if (s_fd_table[fd].vfs_index == i) {
|
||||
const int local_fd = s_fd_table[fd].local_fd; // single read -> no locking is required
|
||||
if (readfds && esp_vfs_safe_fd_isset(local_fd, &item->readfds)) {
|
||||
ESP_LOGD(TAG, "FD %d in readfds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, readfds);
|
||||
++ret;
|
||||
}
|
||||
if (writefds && esp_vfs_safe_fd_isset(local_fd, &item->writefds)) {
|
||||
ESP_LOGD(TAG, "FD %d in writefds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, writefds);
|
||||
++ret;
|
||||
}
|
||||
if (errorfds && esp_vfs_safe_fd_isset(local_fd, &item->errorfds)) {
|
||||
ESP_LOGD(TAG, "FD %d in errorfds was set from VFS ID %d", fd, i);
|
||||
FD_SET(fd, errorfds);
|
||||
++ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
465
components/vfs/vfs_eventfd.c
Normal file
465
components/vfs/vfs_eventfd.c
Normal file
@@ -0,0 +1,465 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
|
||||
#include "esp_vfs_eventfd.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "soc/spinlock.h"
|
||||
|
||||
#define FD_INVALID -1
|
||||
#define FD_PENDING_SELECT -2
|
||||
|
||||
/*
|
||||
* About the event_select_args_t linked list
|
||||
*
|
||||
* Each event_select_args_t structure records a pending select from a select call
|
||||
* on a file descriptor.
|
||||
*
|
||||
* For each select() call, we form a linked list in end_select_args containing
|
||||
* all the pending selects in this select call.
|
||||
*
|
||||
* For each file descriptor, we form a double linked list in event_context_t::select_args.
|
||||
* This list contains all the pending selects on this file descriptor from
|
||||
* different select() calls.
|
||||
*
|
||||
*/
|
||||
typedef struct event_select_args_t {
|
||||
int fd;
|
||||
fd_set *read_fds;
|
||||
fd_set *error_fds;
|
||||
esp_vfs_select_sem_t signal_sem;
|
||||
// linked list node in event_context_t::select_args
|
||||
struct event_select_args_t *prev_in_fd;
|
||||
struct event_select_args_t *next_in_fd;
|
||||
// linked list node in end_select_arg
|
||||
struct event_select_args_t *next_in_args;
|
||||
} event_select_args_t;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
bool support_isr;
|
||||
volatile bool is_set;
|
||||
volatile uint64_t value;
|
||||
// a double-linked list for all pending select args with this fd
|
||||
event_select_args_t *select_args;
|
||||
_lock_t lock;
|
||||
// only for event fds that support ISR.
|
||||
spinlock_t data_spin_lock;
|
||||
} event_context_t;
|
||||
|
||||
esp_vfs_id_t s_eventfd_vfs_id = -1;
|
||||
|
||||
static size_t s_event_size;
|
||||
static event_context_t *s_events;
|
||||
|
||||
static void trigger_select_for_event(event_context_t *event)
|
||||
{
|
||||
event_select_args_t *select_args = event->select_args;
|
||||
while (select_args != NULL) {
|
||||
esp_vfs_select_triggered(select_args->signal_sem);
|
||||
select_args = select_args->next_in_fd;
|
||||
}
|
||||
}
|
||||
|
||||
static void trigger_select_for_event_isr(event_context_t *event, BaseType_t *task_woken)
|
||||
{
|
||||
event_select_args_t *select_args = event->select_args;
|
||||
while (select_args != NULL) {
|
||||
BaseType_t local_woken;
|
||||
esp_vfs_select_triggered_isr(select_args->signal_sem, &local_woken);
|
||||
*task_woken = (local_woken || *task_woken);
|
||||
select_args = select_args->next_in_fd;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
static esp_err_t event_start_select(int nfds,
|
||||
fd_set *readfds,
|
||||
fd_set *writefds,
|
||||
fd_set *exceptfds,
|
||||
esp_vfs_select_sem_t signal_sem,
|
||||
void **end_select_args)
|
||||
{
|
||||
esp_err_t error = ESP_OK;
|
||||
bool should_trigger = false;
|
||||
nfds = nfds < s_event_size ? nfds : (int)s_event_size;
|
||||
event_select_args_t *select_args_list = NULL;
|
||||
|
||||
// FIXME: end_select_args should be a list to all select args
|
||||
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
_lock_acquire_recursive(&s_events[i].lock);
|
||||
if (s_events[i].fd == i) {
|
||||
if (s_events[i].support_isr) {
|
||||
portENTER_CRITICAL(&s_events[i].data_spin_lock);
|
||||
}
|
||||
|
||||
event_select_args_t *event_select_args =
|
||||
(event_select_args_t *)malloc(sizeof(event_select_args_t));
|
||||
event_select_args->fd = i;
|
||||
event_select_args->signal_sem = signal_sem;
|
||||
|
||||
if (FD_ISSET(i, exceptfds)) {
|
||||
FD_CLR(i, exceptfds);
|
||||
event_select_args->error_fds = exceptfds;
|
||||
} else {
|
||||
event_select_args->error_fds = NULL;
|
||||
}
|
||||
FD_CLR(i, exceptfds);
|
||||
// event fds are always writable
|
||||
if (FD_ISSET(i, writefds)) {
|
||||
should_trigger = true;
|
||||
}
|
||||
if (FD_ISSET(i, readfds)) {
|
||||
event_select_args->read_fds = readfds;
|
||||
if (s_events[i].is_set) {
|
||||
should_trigger = true;
|
||||
} else {
|
||||
FD_CLR(i, readfds);
|
||||
}
|
||||
} else {
|
||||
event_select_args->read_fds = NULL;
|
||||
}
|
||||
event_select_args->prev_in_fd = NULL;
|
||||
event_select_args->next_in_fd = s_events[i].select_args;
|
||||
if (s_events[i].select_args) {
|
||||
s_events[i].select_args->prev_in_fd = event_select_args;
|
||||
}
|
||||
event_select_args->next_in_args = select_args_list;
|
||||
select_args_list = event_select_args;
|
||||
s_events[i].select_args = event_select_args;
|
||||
|
||||
if (s_events[i].support_isr) {
|
||||
portEXIT_CRITICAL(&s_events[i].data_spin_lock);
|
||||
}
|
||||
}
|
||||
_lock_release_recursive(&s_events[i].lock);
|
||||
}
|
||||
|
||||
*end_select_args = select_args_list;
|
||||
|
||||
if (should_trigger) {
|
||||
esp_vfs_select_triggered(signal_sem);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static esp_err_t event_end_select(void *end_select_args)
|
||||
{
|
||||
event_select_args_t *select_args = (event_select_args_t *)end_select_args;
|
||||
|
||||
while (select_args != NULL) {
|
||||
event_context_t *event = &s_events[select_args->fd];
|
||||
|
||||
_lock_acquire_recursive(&event->lock);
|
||||
if (event->support_isr) {
|
||||
portENTER_CRITICAL(&event->data_spin_lock);
|
||||
}
|
||||
|
||||
if (event->fd != select_args->fd) { // already closed
|
||||
if (select_args->error_fds) {
|
||||
FD_SET(select_args->fd, select_args->error_fds);
|
||||
}
|
||||
} else {
|
||||
if (select_args->read_fds && event->is_set) {
|
||||
FD_SET(select_args->fd, select_args->read_fds);
|
||||
}
|
||||
}
|
||||
|
||||
event_select_args_t *prev_in_fd = select_args->prev_in_fd;
|
||||
event_select_args_t *next_in_fd = select_args->next_in_fd;
|
||||
event_select_args_t *next_in_args = select_args->next_in_args;
|
||||
if (prev_in_fd != NULL) {
|
||||
prev_in_fd->next_in_fd = next_in_fd;
|
||||
} else {
|
||||
event->select_args = next_in_fd;
|
||||
}
|
||||
if (next_in_fd != NULL) {
|
||||
next_in_fd->prev_in_fd = prev_in_fd;
|
||||
}
|
||||
if (prev_in_fd == NULL && next_in_fd == NULL) { // The last pending select
|
||||
if (event->fd == FD_PENDING_SELECT) {
|
||||
event->fd = FD_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->support_isr) {
|
||||
portEXIT_CRITICAL(&event->data_spin_lock);
|
||||
}
|
||||
_lock_release_recursive(&event->lock);
|
||||
|
||||
free(select_args);
|
||||
select_args = next_in_args;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
static ssize_t signal_event_fd_from_isr(int fd, const void *data, size_t size)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
const uint64_t *val = (const uint64_t *)data;
|
||||
ssize_t ret = size;
|
||||
|
||||
portENTER_CRITICAL_ISR(&s_events[fd].data_spin_lock);
|
||||
|
||||
if (s_events[fd].fd == fd) {
|
||||
s_events[fd].is_set = true;
|
||||
s_events[fd].value += *val;
|
||||
trigger_select_for_event_isr(&s_events[fd], &task_woken);
|
||||
} else {
|
||||
errno = EBADF;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL_ISR(&s_events[fd].data_spin_lock);
|
||||
|
||||
if (task_woken) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t event_write(int fd, const void *data, size_t size)
|
||||
{
|
||||
ssize_t ret = -1;
|
||||
|
||||
if (fd >= s_event_size || data == NULL || size != sizeof(uint64_t)) {
|
||||
errno = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
if (size != sizeof(uint64_t)) {
|
||||
errno = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (xPortInIsrContext()) {
|
||||
ret = signal_event_fd_from_isr(fd, data, size);
|
||||
} else {
|
||||
const uint64_t *val = (const uint64_t *)data;
|
||||
|
||||
_lock_acquire_recursive(&s_events[fd].lock);
|
||||
if (s_events[fd].support_isr) {
|
||||
portENTER_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
|
||||
if (s_events[fd].fd == fd) {
|
||||
s_events[fd].is_set = true;
|
||||
s_events[fd].value += *val;
|
||||
ret = size;
|
||||
trigger_select_for_event(&s_events[fd]);
|
||||
|
||||
if (s_events[fd].support_isr) {
|
||||
portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
} else {
|
||||
errno = EBADF;
|
||||
ret = -1;
|
||||
}
|
||||
_lock_release_recursive(&s_events[fd].lock);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t event_read(int fd, void *data, size_t size)
|
||||
{
|
||||
ssize_t ret = -1;
|
||||
|
||||
if (fd >= s_event_size || data == NULL || size != sizeof(uint64_t)) {
|
||||
errno = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t *val = (uint64_t *)data;
|
||||
|
||||
_lock_acquire_recursive(&s_events[fd].lock);
|
||||
if (s_events[fd].support_isr) {
|
||||
portENTER_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
|
||||
if (s_events[fd].fd == fd) {
|
||||
*val = s_events[fd].value;
|
||||
s_events[fd].is_set = false;
|
||||
ret = size;
|
||||
s_events[fd].value = 0;
|
||||
} else {
|
||||
errno = EBADF;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (s_events[fd].support_isr) {
|
||||
portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
_lock_release_recursive(&s_events[fd].lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int event_close(int fd)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
if (fd >= s_event_size) {
|
||||
errno = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
_lock_acquire_recursive(&s_events[fd].lock);
|
||||
if (s_events[fd].fd == fd) {
|
||||
if (s_events[fd].support_isr) {
|
||||
portENTER_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
if (s_events[fd].select_args == NULL) {
|
||||
s_events[fd].fd = FD_INVALID;
|
||||
} else {
|
||||
s_events[fd].fd = FD_PENDING_SELECT;
|
||||
trigger_select_for_event(&s_events[fd]);
|
||||
}
|
||||
s_events[fd].value = 0;
|
||||
if (s_events[fd].support_isr) {
|
||||
portEXIT_CRITICAL(&s_events[fd].data_spin_lock);
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
errno = EBADF;
|
||||
}
|
||||
_lock_release_recursive(&s_events[fd].lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config)
|
||||
{
|
||||
if (config == NULL || config->max_fds >= MAX_FDS) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (s_eventfd_vfs_id != -1) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
s_event_size = config->max_fds;
|
||||
s_events = (event_context_t *)calloc(s_event_size, sizeof(event_context_t));
|
||||
for (size_t i = 0; i < s_event_size; i++) {
|
||||
_lock_init_recursive(&s_events[i].lock);
|
||||
s_events[i].fd = FD_INVALID;
|
||||
}
|
||||
|
||||
esp_vfs_t vfs = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
.write = &event_write,
|
||||
.close = &event_close,
|
||||
.read = &event_read,
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
.start_select = &event_start_select,
|
||||
.end_select = &event_end_select,
|
||||
#endif
|
||||
};
|
||||
return esp_vfs_register_with_id(&vfs, NULL, &s_eventfd_vfs_id);
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_eventfd_unregister(void)
|
||||
{
|
||||
if (s_eventfd_vfs_id == -1) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = esp_vfs_unregister_with_id(s_eventfd_vfs_id);
|
||||
if (error == ESP_OK) {
|
||||
s_eventfd_vfs_id = -1;
|
||||
}
|
||||
for (size_t i = 0; i < s_event_size; i++) {
|
||||
_lock_close_recursive(&s_events[i].lock);
|
||||
}
|
||||
free(s_events);
|
||||
return error;
|
||||
}
|
||||
|
||||
int eventfd(unsigned int initval, int flags)
|
||||
{
|
||||
int fd = FD_INVALID;
|
||||
int global_fd = FD_INVALID;
|
||||
esp_err_t error = ESP_OK;
|
||||
|
||||
if ((flags & (~EFD_SUPPORT_ISR)) != 0) {
|
||||
errno = EINVAL;
|
||||
return FD_INVALID;
|
||||
}
|
||||
if (s_eventfd_vfs_id == -1) {
|
||||
errno = EACCES;
|
||||
return FD_INVALID;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < s_event_size; i++) {
|
||||
_lock_acquire_recursive(&s_events[i].lock);
|
||||
if (s_events[i].fd == FD_INVALID) {
|
||||
|
||||
error = esp_vfs_register_fd_with_local_fd(s_eventfd_vfs_id, i, /*permanent=*/false, &global_fd);
|
||||
if (error != ESP_OK) {
|
||||
_lock_release_recursive(&s_events[i].lock);
|
||||
break;
|
||||
}
|
||||
|
||||
bool support_isr = flags & EFD_SUPPORT_ISR;
|
||||
fd = i;
|
||||
s_events[i].fd = i;
|
||||
s_events[i].support_isr = support_isr;
|
||||
spinlock_initialize(&s_events[i].data_spin_lock);
|
||||
|
||||
if (support_isr) {
|
||||
portENTER_CRITICAL(&s_events[i].data_spin_lock);
|
||||
}
|
||||
s_events[i].is_set = false;
|
||||
s_events[i].value = initval;
|
||||
s_events[i].select_args = NULL;
|
||||
if (support_isr) {
|
||||
portEXIT_CRITICAL(&s_events[i].data_spin_lock);
|
||||
}
|
||||
_lock_release_recursive(&s_events[i].lock);
|
||||
break;
|
||||
}
|
||||
_lock_release_recursive(&s_events[i].lock);
|
||||
}
|
||||
|
||||
switch (error) {
|
||||
case ESP_OK:
|
||||
fd = global_fd;
|
||||
break;
|
||||
case ESP_ERR_NO_MEM:
|
||||
errno = ENOMEM;
|
||||
break;
|
||||
case ESP_ERR_INVALID_ARG:
|
||||
errno = EINVAL;
|
||||
break;
|
||||
default:
|
||||
errno = EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
@@ -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 \
|
||||
|
@@ -7,3 +7,4 @@ API Reference
|
||||
|
||||
.. include-build-file:: inc/esp_vfs_dev.inc
|
||||
|
||||
.. include-build-file:: inc/esp_vfs_eventfd.inc
|
||||
|
6
examples/system/eventfd/CMakeLists.txt
Normal file
6
examples/system/eventfd/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(eventfd)
|
8
examples/system/eventfd/Makefile
Normal file
8
examples/system/eventfd/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := eventfd
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
74
examples/system/eventfd/README.md
Normal file
74
examples/system/eventfd/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# eventfd example
|
||||
|
||||
The example demonstrates the use of `eventfd()` to collect events from other tasks and ISRs in a
|
||||
`select()` based main loop. The example starts two tasks and installs a timer interrupt handler:
|
||||
1. The first task writes to the first `eventfd` periodically.
|
||||
2. The timer interrupt handler writes to the second `eventfd`.
|
||||
3. The second task collects the event from two fds with a `select()` loop.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32, ESP32S2, ESP32S3 or ESP32C3 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
The default config will work.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The following log output should appear when the example runs (note that the bootloader log has been omitted).
|
||||
|
||||
```
|
||||
I (4310) eventfd_example: Time: 1.99s
|
||||
I (4310) eventfd_example: Select timeout
|
||||
I (4310) eventfd_example: =================================
|
||||
I (4310) eventfd_example: Select timeouted for 1 times
|
||||
I (4320) eventfd_example: Timer triggerred for 0 times
|
||||
I (4320) eventfd_example: Progress triggerred for 0 times
|
||||
I (4330) eventfd_example: =================================
|
||||
I (4810) eventfd_example: Time: 2.50s
|
||||
I (4810) eventfd_example: TimerEvent fd event triggered
|
||||
I (5810) eventfd_example: Time: 3.49s
|
||||
I (5810) eventfd_example: Progress fd event triggered
|
||||
I (7310) eventfd_example: Time: 5.00s
|
||||
I (7310) eventfd_example: TimerEvent fd event triggered
|
||||
I (9310) eventfd_example: Time: 6.99s
|
||||
I (9310) eventfd_example: Select timeout
|
||||
I (9310) eventfd_example: Time: 6.99s
|
||||
I (9310) eventfd_example: Progress fd event triggered
|
||||
I (9810) eventfd_example: Time: 7.50s
|
||||
I (9810) eventfd_example: TimerEvent fd event triggered
|
||||
I (11810) eventfd_example: Time: 9.49s
|
||||
I (11810) eventfd_example: Select timeout
|
||||
I (12310) eventfd_example: Time: 10.00s
|
||||
I (12310) eventfd_example: TimerEvent fd event triggered
|
||||
I (12810) eventfd_example: Time: 10.49s
|
||||
I (12810) eventfd_example: Progress fd event triggered
|
||||
I (14810) eventfd_example: Time: 12.49s
|
||||
I (14810) eventfd_example: Select timeout
|
||||
I (14810) eventfd_example: =================================
|
||||
I (14810) eventfd_example: Select timeouted for 4 times
|
||||
I (14820) eventfd_example: Timer triggerred for 4 times
|
||||
I (14820) eventfd_example: Progress triggerred for 3 times
|
||||
I (14830) eventfd_example: =================================
|
||||
```
|
29
examples/system/eventfd/example_test.py
Normal file
29
examples/system/eventfd/example_test.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import Env, Utility
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_eventfd(env, extra_data):
|
||||
# type: (Env, None) -> None
|
||||
|
||||
dut = env.get_dut('eventfd', 'examples/system/eventfd')
|
||||
dut.start_app()
|
||||
|
||||
dut.expect('cpu_start: Starting scheduler', timeout=30)
|
||||
|
||||
exp_list = [
|
||||
'eventfd_example: Select timeouted for 4 times',
|
||||
'eventfd_example: Timer triggerred for 4 times',
|
||||
'eventfd_example: Progress triggerred for 3 times',
|
||||
]
|
||||
|
||||
Utility.console_log('Expecting:{}{}'.format(os.linesep, os.linesep.join(exp_list)))
|
||||
dut.expect_all(*exp_list, timeout=60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_eventfd()
|
3
examples/system/eventfd/main/CMakeLists.txt
Normal file
3
examples/system/eventfd/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "eventfd_example.c"
|
||||
LDFRAGMENTS linker.lf
|
||||
INCLUDE_DIRS ".")
|
6
examples/system/eventfd/main/component.mk
Normal file
6
examples/system/eventfd/main/component.mk
Normal file
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_ADD_LDFRAGMENTS += linker.lf
|
166
examples/system/eventfd/main/eventfd_example.c
Normal file
166
examples/system/eventfd/main/eventfd_example.c
Normal file
@@ -0,0 +1,166 @@
|
||||
/* eventfd example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "driver/timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "hal/timer_types.h"
|
||||
|
||||
#define TIMER_DIVIDER 16
|
||||
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER)
|
||||
#define MS_PER_S 1000
|
||||
#define TIMER_INTERVAL_SEC 2.5
|
||||
#define TEST_WITHOUT_RELOAD 0
|
||||
#define PROGRESS_INTERVAL_MS 3500
|
||||
#define TIMER_SIGNAL 1
|
||||
#define PROGRESS_SIGNAL 2
|
||||
|
||||
static const char *TAG = "eventfd_example";
|
||||
|
||||
int s_timer_fd;
|
||||
int s_progress_fd;
|
||||
|
||||
static void eventfd_timer_group0_isr(void *para)
|
||||
{
|
||||
timer_spinlock_take(TIMER_GROUP_0);
|
||||
int timer_idx = (int) para;
|
||||
|
||||
uint32_t timer_intr = timer_group_get_intr_status_in_isr(TIMER_GROUP_0);
|
||||
uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(TIMER_GROUP_0, timer_idx);
|
||||
|
||||
if (timer_intr & TIMER_INTR_T0) {
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_counter_value += (uint64_t) (TIMER_INTERVAL_SEC * TIMER_SCALE);
|
||||
timer_group_set_alarm_value_in_isr(TIMER_GROUP_0, timer_idx, timer_counter_value);
|
||||
}
|
||||
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, timer_idx);
|
||||
|
||||
uint64_t signal = TIMER_SIGNAL;
|
||||
ssize_t val = write(s_timer_fd, &signal, sizeof(signal));
|
||||
assert(val == sizeof(signal));
|
||||
timer_spinlock_give(TIMER_GROUP_0);
|
||||
}
|
||||
|
||||
static void eventfd_timer_init(int timer_idx, double timer_interval_sec)
|
||||
{
|
||||
timer_config_t config = {
|
||||
.divider = TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.auto_reload = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(timer_init(TIMER_GROUP_0, timer_idx, &config));
|
||||
|
||||
ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL));
|
||||
|
||||
ESP_ERROR_CHECK(timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE));
|
||||
ESP_ERROR_CHECK(timer_enable_intr(TIMER_GROUP_0, timer_idx));
|
||||
ESP_ERROR_CHECK(timer_isr_register(TIMER_GROUP_0, timer_idx, eventfd_timer_group0_isr,
|
||||
NULL, ESP_INTR_FLAG_IRAM, NULL));
|
||||
|
||||
ESP_ERROR_CHECK(timer_start(TIMER_GROUP_0, timer_idx));
|
||||
}
|
||||
|
||||
static void worker_task(void *arg)
|
||||
{
|
||||
while (true) {
|
||||
vTaskDelay(pdMS_TO_TICKS(PROGRESS_INTERVAL_MS));
|
||||
uint64_t signal = PROGRESS_SIGNAL;
|
||||
ssize_t val = write(s_progress_fd, &signal, sizeof(signal));
|
||||
assert(val == sizeof(signal));
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void collector_task(void *arg)
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&config));
|
||||
|
||||
s_timer_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
s_progress_fd = eventfd(0, 0);
|
||||
assert(s_timer_fd > 0);
|
||||
assert(s_progress_fd > 0);
|
||||
|
||||
int maxFd = s_progress_fd > s_timer_fd ? s_progress_fd : s_timer_fd;
|
||||
int select_timeout_count = 0;
|
||||
int timer_trigger_count = 0;
|
||||
int progress_trigger_count = 0;
|
||||
|
||||
for (size_t i = 0; ; i++) {
|
||||
struct timeval timeout;
|
||||
uint64_t signal;
|
||||
|
||||
timeout.tv_sec = 2;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(s_timer_fd, &readfds);
|
||||
FD_SET(s_progress_fd, &readfds);
|
||||
|
||||
int num_triggered = select(maxFd + 1, &readfds, NULL, NULL, &timeout);
|
||||
assert(num_triggered >= 0);
|
||||
|
||||
uint64_t task_counter_value;
|
||||
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &task_counter_value);
|
||||
ESP_LOGI(TAG, "Time: %.2fs", (double)task_counter_value / TIMER_SCALE);
|
||||
|
||||
if (FD_ISSET(s_progress_fd, &readfds)) {
|
||||
ssize_t ret = read(s_progress_fd, &signal, sizeof(signal));
|
||||
assert(ret == sizeof(signal));
|
||||
assert(signal == PROGRESS_SIGNAL);
|
||||
progress_trigger_count++;
|
||||
ESP_LOGI(TAG, "Progress fd event triggered");
|
||||
}
|
||||
if (FD_ISSET(s_timer_fd, &readfds)) {
|
||||
ssize_t ret = read(s_timer_fd, &signal, sizeof(signal));
|
||||
assert(ret == sizeof(signal));
|
||||
assert(signal == TIMER_SIGNAL);
|
||||
timer_trigger_count++;
|
||||
ESP_LOGI(TAG, "TimerEvent fd event triggered");
|
||||
}
|
||||
if (num_triggered == 0) {
|
||||
select_timeout_count++;
|
||||
ESP_LOGI(TAG, "Select timeout");
|
||||
}
|
||||
|
||||
if (i % 10 == 0) {
|
||||
ESP_LOGI(TAG, "=================================");
|
||||
ESP_LOGI(TAG, "Select timeouted for %d times", select_timeout_count);
|
||||
ESP_LOGI(TAG, "Timer triggerred for %d times", timer_trigger_count);
|
||||
ESP_LOGI(TAG, "Progress triggerred for %d times", progress_trigger_count);
|
||||
ESP_LOGI(TAG, "=================================");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
timer_deinit(TIMER_GROUP_0, TIMER_0);
|
||||
close(s_timer_fd);
|
||||
close(s_progress_fd);
|
||||
esp_vfs_eventfd_unregister();
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
eventfd_timer_init(TIMER_0, TIMER_INTERVAL_SEC);
|
||||
xTaskCreate(worker_task, "worker_task", 4 * 1024, NULL, 5, NULL);
|
||||
xTaskCreate(collector_task, "collector_task", 4 * 1024, NULL, 5, NULL);
|
||||
}
|
4
examples/system/eventfd/main/linker.lf
Normal file
4
examples/system/eventfd/main/linker.lf
Normal file
@@ -0,0 +1,4 @@
|
||||
[mapping:main]
|
||||
archive: libmain.a
|
||||
entries:
|
||||
eventfd_example:eventfd_timer_group0_isr (noflash)
|
Reference in New Issue
Block a user