/* * Copyright (c) 2020 Tobias Svehagen * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include struct eventfd { struct k_poll_signal read_sig; struct k_poll_signal write_sig; struct k_spinlock lock; _wait_q_t wait_q; 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) { if (pfd->events & ZSOCK_POLLIN) { if (*pev == pev_end) { errno = ENOMEM; return -1; } (*pev)->obj = &efd->read_sig; (*pev)->type = K_POLL_TYPE_SIGNAL; (*pev)->mode = K_POLL_MODE_NOTIFY_ONLY; (*pev)->state = K_POLL_STATE_NOT_READY; (*pev)++; } if (pfd->events & ZSOCK_POLLOUT) { if (*pev == pev_end) { errno = ENOMEM; return -1; } (*pev)->obj = &efd->write_sig; (*pev)->type = K_POLL_TYPE_SIGNAL; (*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_POLLIN) { if ((*pev)->state != K_POLL_STATE_NOT_READY) { pfd->revents |= ZSOCK_POLLIN; } (*pev)++; } if (pfd->events & ZSOCK_POLLOUT) { if ((*pev)->state != K_POLL_STATE_NOT_READY) { pfd->revents |= ZSOCK_POLLOUT; } (*pev)++; } return 0; } static ssize_t eventfd_read_op(void *obj, void *buf, size_t sz) { struct eventfd *efd = obj; int result = 0; eventfd_t count = 0; k_spinlock_key_t key; if (sz < sizeof(eventfd_t)) { errno = EINVAL; return -1; } for (;;) { key = k_spin_lock(&efd->lock); if ((efd->flags & EFD_NONBLOCK) && efd->cnt == 0) { result = EAGAIN; break; } else if (efd->cnt == 0) { z_pend_curr(&efd->lock, key, &efd->wait_q, K_FOREVER); } else { count = (efd->flags & EFD_SEMAPHORE) ? 1 : efd->cnt; efd->cnt -= count; if (efd->cnt == 0) { k_poll_signal_reset(&efd->read_sig); } k_poll_signal_raise(&efd->write_sig, 0); break; } } if (z_unpend_all(&efd->wait_q) != 0) { z_reschedule(&efd->lock, key); } else { k_spin_unlock(&efd->lock, key); } if (result != 0) { errno = result; return -1; } *(eventfd_t *)buf = count; return sizeof(eventfd_t); } static ssize_t eventfd_write_op(void *obj, const void *buf, size_t sz) { struct eventfd *efd = obj; int result = 0; eventfd_t count; bool overflow; k_spinlock_key_t key; if (sz < sizeof(eventfd_t)) { errno = EINVAL; return -1; } count = *((eventfd_t *)buf); if (count == UINT64_MAX) { errno = EINVAL; return -1; } if (count == 0) { return sizeof(eventfd_t); } for (;;) { key = k_spin_lock(&efd->lock); overflow = UINT64_MAX - count <= efd->cnt; if ((efd->flags & EFD_NONBLOCK) && overflow) { result = EAGAIN; break; } else if (overflow) { z_pend_curr(&efd->lock, key, &efd->wait_q, K_FOREVER); } else { efd->cnt += count; if (efd->cnt == (UINT64_MAX - 1)) { k_poll_signal_reset(&efd->write_sig); } k_poll_signal_raise(&efd->read_sig, 0); break; } } if (z_unpend_all(&efd->wait_q) != 0) { z_reschedule(&efd->lock, key); } else { k_spin_unlock(&efd->lock, key); } if (result != 0) { errno = result; return -1; } return sizeof(eventfd_t); } static int eventfd_close_op(void *obj) { struct eventfd *efd = (struct eventfd *)obj; efd->flags = 0; return 0; } 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_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, .close = eventfd_close_op, .ioctl = eventfd_ioctl_op, }; int eventfd(unsigned int initval, int flags) { struct eventfd *efd = NULL; int fd = -1; int i; if (flags & ~EFD_FLAGS_SET) { errno = EINVAL; return -1; } k_mutex_lock(&eventfd_mtx, K_FOREVER); for (i = 0; i < ARRAY_SIZE(efds); ++i) { if (!(efds[i].flags & EFD_IN_USE)) { efd = &efds[i]; break; } } if (efd == NULL) { errno = ENOMEM; goto exit_mtx; } fd = z_reserve_fd(); if (fd < 0) { goto exit_mtx; } efd->flags = EFD_IN_USE | flags; efd->cnt = initval; k_poll_signal_init(&efd->write_sig); k_poll_signal_init(&efd->read_sig); z_waitq_init(&efd->wait_q); if (initval != 0) { k_poll_signal_raise(&efd->read_sig, 0); } k_poll_signal_raise(&efd->write_sig, 0); z_finalize_fd(fd, efd, &eventfd_fd_vtable); exit_mtx: k_mutex_unlock(&eventfd_mtx); return fd; }