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/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; +} 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