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 <tobias.svehagen@gmail.com>
This commit is contained in:
parent
773de88db0
commit
ca872a44c1
9 changed files with 559 additions and 0 deletions
|
@ -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_ */
|
||||
|
|
91
include/posix/sys/eventfd.h
Normal file
91
include/posix/sys/eventfd.h
Normal file
|
@ -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 <sys/fdtable.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#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_ */
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
241
lib/posix/eventfd.c
Normal file
241
lib/posix/eventfd.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Tobias Svehagen
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <net/socket.h>
|
||||
|
||||
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;
|
||||
}
|
8
tests/posix/eventfd/CMakeLists.txt
Normal file
8
tests/posix/eventfd/CMakeLists.txt
Normal file
|
@ -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})
|
16
tests/posix/eventfd/prj.conf
Normal file
16
tests/posix/eventfd/prj.conf
Normal file
|
@ -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
|
176
tests/posix/eventfd/src/main.c
Normal file
176
tests/posix/eventfd/src/main.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Tobias Svehagen
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <ztest.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifndef PTHREAD_STACK_MIN
|
||||
#define PTHREAD_STACK_MIN 0
|
||||
#endif
|
||||
|
||||
#if CONFIG_POSIX_API
|
||||
#include <net/socket.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#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);
|
||||
}
|
5
tests/posix/eventfd/testcase.yaml
Normal file
5
tests/posix/eventfd/testcase.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
tests:
|
||||
posix.eventfd:
|
||||
arch_exclude: posix
|
||||
min_ram: 32
|
||||
tags: posix pthread eventfd
|
Loading…
Add table
Add a link
Reference in a new issue