From 473b69173d56a3ba1f92346ebb696403f1c62a60 Mon Sep 17 00:00:00 2001 From: Tobias Svehagen Date: Tue, 5 Nov 2019 16:09:42 +0100 Subject: [PATCH 1/2] lib: posix: Add support for eventfd This implements a file descriptor used for event notification that behaves like the eventfd in Linux. The eventfd supports nonblocking operation by setting the EFD_NONBLOCK flag and semaphore operation by settings the EFD_SEMAPHORE flag. The major use case for this is when using poll() and the sockets that you poll are dynamic. When a new socket needs to be added to the poll, there must be some way to wake the thread and update the pollfds before calling poll again. One way to solve it is to have a timeout set in the poll call and only update the pollfds during a timeout but that is not a very nice solution. By instead including an eventfd in the pollfds, it is possible to wake the polling thread by simply writing to the eventfd. Signed-off-by: Tobias Svehagen --- arch/posix/include/posix_cheats.h | 5 + include/posix/sys/eventfd.h | 91 +++++++++++ lib/posix/CMakeLists.txt | 1 + lib/posix/Kconfig | 16 ++ lib/posix/eventfd.c | 241 +++++++++++++++++++++++++++++ tests/posix/eventfd/CMakeLists.txt | 8 + tests/posix/eventfd/prj.conf | 16 ++ tests/posix/eventfd/src/main.c | 176 +++++++++++++++++++++ tests/posix/eventfd/testcase.yaml | 5 + 9 files changed, 559 insertions(+) create mode 100644 include/posix/sys/eventfd.h create mode 100644 lib/posix/eventfd.c create mode 100644 tests/posix/eventfd/CMakeLists.txt create mode 100644 tests/posix/eventfd/prj.conf create mode 100644 tests/posix/eventfd/src/main.c create mode 100644 tests/posix/eventfd/testcase.yaml diff --git a/arch/posix/include/posix_cheats.h b/arch/posix/include/posix_cheats.h index 0e3c2d6e14e5..1a2ea798ad7e 100644 --- a/arch/posix/include/posix_cheats.h +++ b/arch/posix/include/posix_cheats.h @@ -221,6 +221,11 @@ void zephyr_app_main(void); #define stat zap_stat #define mkdir zap_mkdir +/* eventfd */ +#define eventfd zap_eventfd +#define eventfd_read zap_eventfd_read +#define eventfd_write zap_eventfd_write + #endif /* CONFIG_POSIX_API */ #endif /* ZEPHYR_ARCH_POSIX_INCLUDE_POSIX_CHEATS_H_ */ diff --git a/include/posix/sys/eventfd.h b/include/posix/sys/eventfd.h new file mode 100644 index 000000000000..038b2c6961e4 --- /dev/null +++ b/include/posix/sys/eventfd.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_POSIX_SYS_EVENTFD_H_ +#define ZEPHYR_INCLUDE_POSIX_SYS_EVENTFD_H_ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define EFD_IN_USE 0x1 +#define EFD_SEMAPHORE 0x2 +#define EFD_NONBLOCK O_NONBLOCK +#define EFD_FLAGS_SET (EFD_SEMAPHORE | EFD_NONBLOCK) + +typedef u64_t eventfd_t; + +/** + * @brief Create a file descriptor for event notification + * + * The returned file descriptor can be used with POSIX read/write calls or + * with the eventfd_read/eventfd_write functions. + * + * It also supports polling and by including an eventfd in a call to poll, + * it is possible to signal and wake the polling thread by simply writing to + * the eventfd. + * + * When using read() and write() on an eventfd, the size must always be at + * least 8 bytes or the operation will fail with EINVAL. + * + * @return New eventfd file descriptor on success, -1 on error + */ +int eventfd(unsigned int initval, int flags); + +/** + * @brief Read from an eventfd + * + * If call is successful, the value parameter will have the value 1 + * + * @param fd File descriptor + * @param value Pointer for storing the read value + * + * @return 0 on success, -1 on error + */ +static inline int eventfd_read(int fd, eventfd_t *value) +{ + const struct fd_op_vtable *efd_vtable; + ssize_t ret; + void *obj; + + obj = z_get_fd_obj_and_vtable(fd, &efd_vtable); + + ret = efd_vtable->read(obj, value, sizeof(*value)); + + return ret == sizeof(eventfd_t) ? 0 : -1; +} + +/** + * @brief Write to an eventfd + * + * @param fd File descriptor + * @param value Value to write + * + * @return 0 on success, -1 on error + */ +static inline int eventfd_write(int fd, eventfd_t value) +{ + const struct fd_op_vtable *efd_vtable; + ssize_t ret; + void *obj; + + obj = z_get_fd_obj_and_vtable(fd, &efd_vtable); + + ret = efd_vtable->write(obj, &value, sizeof(value)); + + return ret == sizeof(eventfd_t) ? 0 : -1; +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_POSIX_SYS_EVENTFD_H_ */ diff --git a/lib/posix/CMakeLists.txt b/lib/posix/CMakeLists.txt index 77d4c4b815fa..e1703eaaa264 100644 --- a/lib/posix/CMakeLists.txt +++ b/lib/posix/CMakeLists.txt @@ -22,6 +22,7 @@ zephyr_library_sources_ifdef(CONFIG_PTHREAD_IPC semaphore.c) zephyr_library_sources_ifdef(CONFIG_PTHREAD_IPC pthread_key.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MQUEUE mqueue.c) zephyr_library_sources_ifdef(CONFIG_POSIX_FS fs.c) +zephyr_library_sources_ifdef(CONFIG_EVENTFD eventfd.c) zephyr_library_include_directories( ${ZEPHYR_BASE}/kernel/include diff --git a/lib/posix/Kconfig b/lib/posix/Kconfig index 0fadc7707403..95c3a7705636 100644 --- a/lib/posix/Kconfig +++ b/lib/posix/Kconfig @@ -108,3 +108,19 @@ config APP_LINK_WITH_POSIX_SUBSYS depends on POSIX_API help Add POSIX subsystem header files to the 'app' include path. + +config EVENTFD + bool "Enable support for eventfd" + depends on !ARCH_POSIX + help + Enable support for event file descriptors, eventfd. An eventfd can + be used as an event wait/notify mechanism together with POSIX calls + like read, write and poll. + +config EVENTFD_MAX + int "Maximum number of eventfd's" + depends on EVENTFD + default 1 + range 1 4096 + help + The maximum number of supported event file descriptors. diff --git a/lib/posix/eventfd.c b/lib/posix/eventfd.c new file mode 100644 index 000000000000..89c5a4168160 --- /dev/null +++ b/lib/posix/eventfd.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2020 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include + +struct eventfd { + struct k_sem read_sem; + struct k_sem write_sem; + eventfd_t cnt; + int flags; +}; + +K_MUTEX_DEFINE(eventfd_mtx); +static struct eventfd efds[CONFIG_EVENTFD_MAX]; + +static int eventfd_poll_prepare(struct eventfd *efd, + struct zsock_pollfd *pfd, + struct k_poll_event **pev, + struct k_poll_event *pev_end) +{ + ARG_UNUSED(efd); + + if (pfd->events & ZSOCK_POLLIN) { + if (*pev == pev_end) { + errno = ENOMEM; + return -1; + } + + (*pev)->obj = &efd->read_sem; + (*pev)->type = K_POLL_TYPE_SEM_AVAILABLE; + (*pev)->mode = K_POLL_MODE_NOTIFY_ONLY; + (*pev)->state = K_POLL_STATE_NOT_READY; + (*pev)++; + } + + return 0; +} + +static int eventfd_poll_update(struct eventfd *efd, + struct zsock_pollfd *pfd, + struct k_poll_event **pev) +{ + ARG_UNUSED(efd); + + if (pfd->events & ZSOCK_POLLOUT) { + pfd->revents |= ZSOCK_POLLOUT; + } + + if (pfd->events & ZSOCK_POLLIN) { + if ((*pev)->state != K_POLL_STATE_NOT_READY) { + pfd->revents |= ZSOCK_POLLIN; + } + (*pev)++; + } + + return 0; +} + +static ssize_t eventfd_read_op(void *obj, void *buf, size_t sz) +{ + struct eventfd *efd = obj; + eventfd_t count; + int ret = 0; + + if (sz < sizeof(eventfd_t)) { + errno = EINVAL; + return -1; + } + + if (efd->cnt == 0) { + if (efd->flags & EFD_NONBLOCK) { + ret = -EAGAIN; + } else { + ret = k_sem_take(&efd->read_sem, K_FOREVER); + } + } + + if (ret < 0) { + errno = -ret; + return -1; + } + + count = (efd->flags & EFD_SEMAPHORE) ? 1 : efd->cnt; + efd->cnt -= count; + *(eventfd_t *)buf = count; + k_sem_give(&efd->write_sem); + + return sizeof(eventfd_t); +} + +static ssize_t eventfd_write_op(void *obj, const void *buf, size_t sz) +{ + eventfd_t count; + int ret = 0; + + struct eventfd *efd = obj; + + if (sz < sizeof(eventfd_t)) { + errno = EINVAL; + return -1; + } + + count = *((eventfd_t *)buf); + + if (count == UINT64_MAX) { + errno = EINVAL; + return -1; + } + + if (UINT64_MAX - count <= efd->cnt) { + if (efd->flags & EFD_NONBLOCK) { + ret = -EAGAIN; + } else { + ret = k_sem_take(&efd->write_sem, K_FOREVER); + } + } + + if (ret < 0) { + errno = -ret; + return -1; + } + + efd->cnt += count; + k_sem_give(&efd->read_sem); + + return sizeof(eventfd_t); +} + +static int eventfd_ioctl_op(void *obj, unsigned int request, va_list args) +{ + struct eventfd *efd = (struct eventfd *)obj; + + switch (request) { + case F_GETFL: + return efd->flags & EFD_FLAGS_SET; + + case F_SETFL: { + int flags; + + flags = va_arg(args, int); + + if (flags & ~EFD_FLAGS_SET) { + errno = EINVAL; + return -1; + } + + efd->flags = flags; + + return 0; + } + + case ZFD_IOCTL_CLOSE: + efd->flags = 0; + return 0; + + case ZFD_IOCTL_POLL_PREPARE: { + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + struct k_poll_event *pev_end; + + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + pev_end = va_arg(args, struct k_poll_event *); + + return eventfd_poll_prepare(obj, pfd, pev, pev_end); + } + + case ZFD_IOCTL_POLL_UPDATE: { + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + + return eventfd_poll_update(obj, pfd, pev); + } + + default: + errno = EOPNOTSUPP; + return -1; + } +} + +static const struct fd_op_vtable eventfd_fd_vtable = { + .read = eventfd_read_op, + .write = eventfd_write_op, + .ioctl = eventfd_ioctl_op, +}; + +int eventfd(unsigned int initval, int flags) +{ + int i, fd; + void *obj = NULL; + + if (flags & ~EFD_FLAGS_SET) { + errno = EINVAL; + return -1; + } + + k_mutex_lock(&eventfd_mtx, K_FOREVER); + + fd = z_reserve_fd(); + if (fd < 0) { + k_mutex_unlock(&eventfd_mtx); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(efds); ++i) { + if (efds[i].flags & EFD_IN_USE) { + continue; + } + + obj = &efds[i]; + efds[i].flags = EFD_IN_USE | flags; + efds[i].cnt = 0; + k_sem_init(&efds[i].read_sem, 0, UINT32_MAX); + k_sem_init(&efds[i].write_sem, 0, UINT32_MAX); + + break; + } + + if (obj == NULL) { + z_free_fd(fd); + errno = ENOMEM; + k_mutex_unlock(&eventfd_mtx); + return -1; + } + + z_finalize_fd(fd, obj, &eventfd_fd_vtable); + + k_mutex_unlock(&eventfd_mtx); + + return fd; +} diff --git a/tests/posix/eventfd/CMakeLists.txt b/tests/posix/eventfd/CMakeLists.txt new file mode 100644 index 000000000000..7851f2b4c046 --- /dev/null +++ b/tests/posix/eventfd/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(eventfd) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/posix/eventfd/prj.conf b/tests/posix/eventfd/prj.conf new file mode 100644 index 000000000000..4cea4afcfe74 --- /dev/null +++ b/tests/posix/eventfd/prj.conf @@ -0,0 +1,16 @@ +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_NET_SOCKETS=y + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_ZTEST=y + +CONFIG_POSIX_API=y +CONFIG_POSIX_MAX_FDS=10 +CONFIG_MAX_PTHREAD_COUNT=1 + +CONFIG_EVENTFD=y +CONFIG_EVENTFD_MAX=3 diff --git a/tests/posix/eventfd/src/main.c b/tests/posix/eventfd/src/main.c new file mode 100644 index 000000000000..97d1aba6b18b --- /dev/null +++ b/tests/posix/eventfd/src/main.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#ifndef PTHREAD_STACK_MIN +#define PTHREAD_STACK_MIN 0 +#endif + +#if CONFIG_POSIX_API +#include +#else +#include +#endif + +#include + +#include + +#define EVENTFD_STACK_SIZE (1024 + CONFIG_TEST_EXTRA_STACKSIZE + \ + PTHREAD_STACK_MIN) + +K_THREAD_STACK_DEFINE(eventfd_stack, EVENTFD_STACK_SIZE); +static pthread_t eventfd_thread; + +static void test_eventfd(void) +{ + int fd = eventfd(0, 0); + + zassert_true(fd >= 0, "fd == %d", fd); + + close(fd); +} + +static void test_eventfd_read_nonblock(void) +{ + eventfd_t val; + int fd, ret; + + fd = eventfd(0, EFD_NONBLOCK); + zassert_true(fd >= 0, "fd == %d", fd); + + ret = eventfd_read(fd, &val); + zassert_true(ret == -1, "read ret %d", ret); + zassert_true(errno == EAGAIN, "errno %d", errno); + + close(fd); +} + +static void test_eventfd_write_then_read(void) +{ + eventfd_t val; + int fd, ret; + + fd = eventfd(0, 0); + zassert_true(fd >= 0, "fd == %d", fd); + + ret = eventfd_write(fd, 3); + zassert_true(ret == 0, "write ret %d", ret); + + ret = eventfd_write(fd, 2); + zassert_true(ret == 0, "write ret %d", ret); + + ret = eventfd_read(fd, &val); + zassert_true(ret == 0, "read ret %d", ret); + zassert_true(val == 5, "val == %d", val); + + close(fd); + + /* Test EFD_SEMAPHORE */ + + fd = eventfd(0, EFD_SEMAPHORE); + zassert_true(fd >= 0, "fd == %d", fd); + + ret = eventfd_write(fd, 3); + zassert_true(ret == 0, "write ret %d", ret); + + ret = eventfd_write(fd, 2); + zassert_true(ret == 0, "write ret %d", ret); + + ret = eventfd_read(fd, &val); + zassert_true(ret == 0, "read ret %d", ret); + zassert_true(val == 1, "val == %d", val); + + close(fd); +} + +static void test_eventfd_poll_timeout(void) +{ + struct pollfd pfd; + int fd, ret; + + fd = eventfd(0, 0); + zassert_true(fd >= 0, "fd == %d", fd); + + pfd.fd = fd; + pfd.events = POLLIN; + + ret = poll(&pfd, 1, K_MSEC(500)); + zassert_true(ret == 0, "poll ret %d", ret); + + close(fd); +} + +static void *test_thread_wait_and_write(void *arg) +{ + int ret, fd = *((int *)arg); + + k_sleep(K_MSEC(500)); + + ret = eventfd_write(fd, 10); + zassert_true(ret == 0, "write ret %d", ret); + + return NULL; +} + +static void test_eventfd_poll_event(void) +{ + struct sched_param schedparam; + pthread_attr_t attr; + struct pollfd pfd; + eventfd_t val; + int fd, ret; + + fd = eventfd(0, 0); + zassert_true(fd >= 0, "fd == %d", fd); + + ret = pthread_attr_init(&attr); + zassert_true(ret == 0, "pthread_attr_init ret %d", ret); + + ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); + zassert_true(ret == 0, "pthread_attr_setschedpolicy ret %d", ret); + + schedparam.sched_priority = 1; + ret = pthread_attr_setschedparam(&attr, &schedparam); + zassert_true(ret == 0, "pthread_attr_setschedparam ret %d", ret); + + ret = pthread_attr_setstack(&attr, eventfd_stack, EVENTFD_STACK_SIZE); + zassert_true(ret == 0, "pthread_attr_setstack ret %d", ret); + + ret = pthread_create(&eventfd_thread, &attr, test_thread_wait_and_write, + &fd); + zassert_true(ret == 0, "pthread_create ret %d", ret); + + pfd.fd = fd; + pfd.events = POLLIN; + + ret = poll(&pfd, 1, K_SECONDS(3)); + zassert_true(ret == 1, "poll ret %d %d", ret, pfd.revents); + zassert_equal(pfd.revents, POLLIN, "POLLIN not set"); + + ret = eventfd_read(fd, &val); + zassert_true(ret == 0, "read ret %d", ret); + zassert_true(val == 10, "val == %d", val); + + close(fd); +} + +void test_main(void) +{ + ztest_test_suite(test_eventfd, + ztest_unit_test(test_eventfd), + ztest_unit_test(test_eventfd_read_nonblock), + ztest_unit_test(test_eventfd_write_then_read), + ztest_unit_test(test_eventfd_poll_timeout), + ztest_unit_test(test_eventfd_poll_event) + ); + ztest_run_test_suite(test_eventfd); +} diff --git a/tests/posix/eventfd/testcase.yaml b/tests/posix/eventfd/testcase.yaml new file mode 100644 index 000000000000..cd84c47564bb --- /dev/null +++ b/tests/posix/eventfd/testcase.yaml @@ -0,0 +1,5 @@ +tests: + posix.eventfd: + arch_exclude: posix + min_ram: 32 + tags: posix pthread eventfd From 98316d40df3851c96fecbf3e7508e64dbf5efe3e Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Wed, 22 Apr 2020 18:28:48 +0300 Subject: [PATCH 2/2] samples: posix: eventfd: Sample application for eventfd() call. Roughly based on eventfd example code from Linux manpages. Similarly to other POSIX-compatible sample, Makefile.posix is provided to build the code on a POSIX system (in this case, as eventfd() is Linux-specific, this has to be a Linux system). Signed-off-by: Paul Sokolovsky --- samples/posix/eventfd/CMakeLists.txt | 9 ++++ samples/posix/eventfd/Makefile.posix | 4 ++ samples/posix/eventfd/prj.conf | 7 +++ samples/posix/eventfd/src/main.c | 80 ++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 samples/posix/eventfd/CMakeLists.txt create mode 100644 samples/posix/eventfd/Makefile.posix create mode 100644 samples/posix/eventfd/prj.conf create mode 100644 samples/posix/eventfd/src/main.c diff --git a/samples/posix/eventfd/CMakeLists.txt b/samples/posix/eventfd/CMakeLists.txt new file mode 100644 index 000000000000..3b95d4a4dcb3 --- /dev/null +++ b/samples/posix/eventfd/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(eventfd) + +#target_include_directories(app PRIVATE ${ZEPHYR_BASE}/include/posix) +target_sources(app PRIVATE src/main.c) diff --git a/samples/posix/eventfd/Makefile.posix b/samples/posix/eventfd/Makefile.posix new file mode 100644 index 000000000000..8cb1f7d7da65 --- /dev/null +++ b/samples/posix/eventfd/Makefile.posix @@ -0,0 +1,4 @@ +# This makefile builds the sample for a POSIX system, like Linux + +eventfd: src/main.c + $(CC) $^ -o $@ diff --git a/samples/posix/eventfd/prj.conf b/samples/posix/eventfd/prj.conf new file mode 100644 index 000000000000..f840478080ed --- /dev/null +++ b/samples/posix/eventfd/prj.conf @@ -0,0 +1,7 @@ +# General config +CONFIG_NEWLIB_LIBC=y +CONFIG_POSIX_API=y +CONFIG_EVENTFD=y + +# Networking config +CONFIG_NETWORKING=y diff --git a/samples/posix/eventfd/src/main.c b/samples/posix/eventfd/src/main.c new file mode 100644 index 000000000000..b43b5ff94978 --- /dev/null +++ b/samples/posix/eventfd/src/main.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + * + * This sample application is roughly based on the sample code in Linux + * manpage for eventfd(). + */ +#include +#include +#include +#include +#include + +#define fatal(msg) \ + do { perror(msg); exit(EXIT_FAILURE); } while (0) + +/* As Zephyr doesn't provide command-line args, emulate them. */ +char *input_argv[] = {"argv0", "1", "2", "3", "4"}; + +int efd; +int g_argc; +char **g_argv; + +void writer(void) +{ + int j; + uint64_t u; + ssize_t s; + + for (j = 1; j < g_argc; j++) { + printf("Writing %s to efd\n", g_argv[j]); + u = strtoull(g_argv[j], NULL, 0); + s = write(efd, &u, sizeof(uint64_t)); + if (s != sizeof(uint64_t)) { + fatal("write"); + } + } + printf("Completed write loop\n"); +} + +void reader(void) +{ + uint64_t u; + ssize_t s; + + sleep(1); + + printf("About to read\n"); + s = read(efd, &u, sizeof(uint64_t)); + if (s != sizeof(uint64_t)) { + fatal("read"); + } + printf("Read %llu (0x%llx) from efd\n", + (unsigned long long)u, (unsigned long long)u); +} + +int main(int argc, char *argv[]) +{ + argv = input_argv; + argc = sizeof(input_argv) / sizeof(input_argv[0]); + + if (argc < 2) { + fprintf(stderr, "Usage: %s ...\n", argv[0]); + exit(EXIT_FAILURE); + } + + g_argc = argc; + g_argv = argv; + + efd = eventfd(0, 0); + if (efd == -1) { + fatal("eventfd"); + } + + writer(); + reader(); + + return 0; +}