From d44528256606d7d27c95139b3abca7e2e0032e51 Mon Sep 17 00:00:00 2001 From: Petro Karashchenko Date: Fri, 17 Dec 2021 02:20:26 +0200 Subject: [PATCH] fs/vfs: Add file descriptor based timers support Signed-off-by: Petro Karashchenko --- fs/Kconfig | 30 +- fs/Makefile | 1 + fs/dirent/fs_closedir.c | 5 +- fs/dirent/fs_opendir.c | 13 +- fs/dirent/fs_readdir.c | 10 +- fs/vfs/Kconfig | 62 +++ fs/vfs/Make.defs | 6 + fs/vfs/fs_eventfd.c | 19 +- fs/vfs/fs_statfs.c | 8 +- fs/vfs/fs_timerfd.c | 822 +++++++++++++++++++++++++++++++++ include/nuttx/wdog.h | 2 +- include/sys/syscall_lookup.h | 5 + include/sys/timerfd.h | 80 ++++ libs/libc/stdio/lib_getdelim.c | 2 +- syscall/syscall.csv | 3 + 15 files changed, 1005 insertions(+), 63 deletions(-) create mode 100644 fs/vfs/Kconfig create mode 100644 fs/vfs/fs_timerfd.c create mode 100644 include/sys/timerfd.h diff --git a/fs/Kconfig b/fs/Kconfig index 3bbda1fa5d..67243a078c 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -85,35 +85,7 @@ config SENDFILE_BUFSIZE ---help--- Size of the I/O buffer to allocate in sendfile(). Default: 512b -config EVENT_FD - bool "EventFD" - default n - ---help--- - Create a file descriptor for event notification - -if EVENT_FD - -config EVENT_FD_VFS_PATH - string "Path to eventfd storage" - default "/var/event" - ---help--- - The path to where eventfd will exist in the VFS namespace. - -config EVENT_FD_POLL - bool "EventFD poll support" - default y - ---help--- - Poll support for file descriptor based events - -config EVENT_FD_NPOLLWAITERS - int "Number of eventFD poll waiters" - default 2 - depends on EVENT_FD_POLL - ---help--- - Maximum number of threads that can be waiting on poll() - -endif # EVENT_FD - +source "fs/vfs/Kconfig" source "fs/aio/Kconfig" source "fs/semaphore/Kconfig" source "fs/mqueue/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index c09c0e3e89..4334eb967e 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -61,6 +61,7 @@ include rpmsgfs/Make.defs endif CFLAGS += ${shell $(INCDIR) "$(CC)" $(TOPDIR)$(DELIM)fs} +CFLAGS += ${shell $(INCDIR) "$(CC)" $(TOPDIR)$(DELIM)sched} AOBJS = $(ASRCS:.S=$(OBJEXT)) COBJS = $(CSRCS:.c=$(OBJEXT)) diff --git a/fs/dirent/fs_closedir.c b/fs/dirent/fs_closedir.c index 3ee897bdbc..bd659ccfe9 100644 --- a/fs/dirent/fs_closedir.c +++ b/fs/dirent/fs_closedir.c @@ -70,7 +70,7 @@ int closedir(FAR DIR *dirp) if (!idir) { - ret = EBADF; + ret = -EBADF; goto errout; } @@ -107,7 +107,6 @@ int closedir(FAR DIR *dirp) ret = inode->u.i_mops->closedir(inode, idir); if (ret < 0) { - ret = -ret; goto errout_with_inode; } } @@ -142,6 +141,6 @@ errout_with_inode: #endif errout: - set_errno(ret); + set_errno(-ret); return ERROR; } diff --git a/fs/dirent/fs_opendir.c b/fs/dirent/fs_opendir.c index 8e28e25d79..354c49025a 100644 --- a/fs/dirent/fs_opendir.c +++ b/fs/dirent/fs_opendir.c @@ -71,7 +71,7 @@ static inline int open_mountpoint(FAR struct inode *inode, if (!inode->u.i_mops || !inode->u.i_mops->opendir) { - return ENOSYS; + return -ENOSYS; } /* Take reference to the mountpoint inode. Note that we do not use @@ -98,7 +98,7 @@ static inline int open_mountpoint(FAR struct inode *inode, /* Negate the error value so that it can be used to set errno */ - return -ret; + return ret; } return OK; @@ -227,7 +227,6 @@ FAR DIR *opendir(FAR const char *path) ret = inode_semtake(); if (ret < 0) { - ret = -ret; goto errout; } @@ -249,7 +248,7 @@ FAR DIR *opendir(FAR const char *path) { /* Inode for 'path' does not exist. */ - ret = ENOTDIR; + ret = -ENOTDIR; goto errout_with_semaphore; } @@ -262,7 +261,7 @@ FAR DIR *opendir(FAR const char *path) { /* Insufficient memory to complete the operation. */ - ret = ENOMEM; + ret = -ENOMEM; goto errout_with_semaphore; } @@ -315,7 +314,7 @@ FAR DIR *opendir(FAR const char *path) } else { - ret = ENOTDIR; + ret = -ENOTDIR; goto errout_with_direntry; } } @@ -334,6 +333,6 @@ errout_with_semaphore: inode_semgive(); errout: - set_errno(ret); + set_errno(-ret); return NULL; } diff --git a/fs/dirent/fs_readdir.c b/fs/dirent/fs_readdir.c index 004252088c..0a64ffc2c5 100644 --- a/fs/dirent/fs_readdir.c +++ b/fs/dirent/fs_readdir.c @@ -181,7 +181,7 @@ FAR struct dirent *readdir(DIR *dirp) if (!idir) { - ret = EBADF; + ret = -EBADF; goto errout; } @@ -215,7 +215,7 @@ FAR struct dirent *readdir(DIR *dirp) if (!inode->u.i_mops || !inode->u.i_mops->readdir) { - ret = EACCES; + ret = -EACCES; goto errout; } @@ -239,10 +239,6 @@ FAR struct dirent *readdir(DIR *dirp) { ret = OK; } - else - { - ret = -ret; - } goto errout; } @@ -253,6 +249,6 @@ FAR struct dirent *readdir(DIR *dirp) return &idir->fd_dir; errout: - set_errno(ret); + set_errno(-ret); return NULL; } diff --git a/fs/vfs/Kconfig b/fs/vfs/Kconfig new file mode 100644 index 0000000000..b722b09903 --- /dev/null +++ b/fs/vfs/Kconfig @@ -0,0 +1,62 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EVENT_FD + bool "EventFD" + default n + ---help--- + Create a file descriptor for event notification + +if EVENT_FD + +config EVENT_FD_VFS_PATH + string "Path to eventfd storage" + default "/var/event" + ---help--- + The path to where eventfd will exist in the VFS namespace. + +config EVENT_FD_POLL + bool "EventFD poll support" + default y + ---help--- + Poll support for file descriptor based events + +config EVENT_FD_NPOLLWAITERS + int "Number of eventFD poll waiters" + default 2 + depends on EVENT_FD_POLL + ---help--- + Maximum number of threads that can be waiting on poll() + +endif # EVENT_FD + +config TIMER_FD + bool "TimerFD" + default n + ---help--- + Create a file descriptor for timer notification + +if TIMER_FD + +config TIMER_FD_VFS_PATH + string "Path to timerfd storage" + default "/var/timer" + ---help--- + The path to where timerfd will exist in the VFS namespace. + +config TIMER_FD_POLL + bool "TimerFD poll support" + default y + ---help--- + Poll support for file descriptor based timers + +config TIMER_FD_NPOLLWAITERS + int "Number of timerFD poll waiters" + default 2 + depends on TIMER_FD_POLL + ---help--- + Maximum number of threads that can be waiting on poll() + +endif # TIMER_FD diff --git a/fs/vfs/Make.defs b/fs/vfs/Make.defs index fe588c1491..61455c1080 100644 --- a/fs/vfs/Make.defs +++ b/fs/vfs/Make.defs @@ -48,6 +48,12 @@ ifeq ($(CONFIG_EVENT_FD),y) CSRCS += fs_eventfd.c endif +# Support for timerfd + +ifeq ($(CONFIG_TIMER_FD),y) +CSRCS += fs_timerfd.c +endif + # Include vfs build support DEPPATH += --dep-path vfs diff --git a/fs/vfs/fs_eventfd.c b/fs/vfs/fs_eventfd.c index 7465e9f198..86e022b473 100644 --- a/fs/vfs/fs_eventfd.c +++ b/fs/vfs/fs_eventfd.c @@ -226,7 +226,7 @@ static int eventfd_do_close(FAR struct file *filep) FAR struct inode *inode = filep->f_inode; FAR struct eventfd_priv_s *priv = inode->i_private; - /* devpath: EVENT_FD_VFS_PATH + /efd (4) + %d (10) + null char (1) */ + /* devpath: EVENT_FD_VFS_PATH + /efd (4) + %u (10) + null char (1) */ char devpath[sizeof(CONFIG_EVENT_FD_VFS_PATH) + 4 + 10 + 1]; @@ -256,7 +256,7 @@ static int eventfd_do_close(FAR struct file *filep) /* Re-create the path to the driver. */ finfo("destroy\n"); - sprintf(devpath, CONFIG_EVENT_FD_VFS_PATH "/efd%d", priv->minor); + sprintf(devpath, CONFIG_EVENT_FD_VFS_PATH "/efd%u", priv->minor); /* Will be unregistered later after close is done */ @@ -501,7 +501,7 @@ static int eventfd_do_poll(FAR struct file *filep, FAR struct pollfd *fds, *slot = NULL; fds->priv = NULL; - goto errout; + goto out; } /* This is a request to set up the poll. Find an available @@ -526,7 +526,7 @@ static int eventfd_do_poll(FAR struct file *filep, FAR struct pollfd *fds, { fds->priv = NULL; ret = -EBUSY; - goto errout; + goto out; } /* Notify the POLLOUT event if the pipe is not full, but only if @@ -551,7 +551,7 @@ static int eventfd_do_poll(FAR struct file *filep, FAR struct pollfd *fds, eventfd_pollnotify(dev, eventset); } -errout: +out: nxsem_post(&dev->exclsem); return ret; } @@ -567,7 +567,7 @@ int eventfd(unsigned int count, int flags) int new_fd; FAR struct eventfd_priv_s *new_dev; - /* devpath: EVENT_FD_VFS_PATH + /efd (4) + size_t (10) + null char (1) */ + /* devpath: EVENT_FD_VFS_PATH + /efd (4) + %u (10) + null char (1) */ char devpath[sizeof(CONFIG_EVENT_FD_VFS_PATH) + 4 + 10 + 1]; @@ -578,7 +578,7 @@ int eventfd(unsigned int count, int flags) { /* Failed to allocate new device */ - ret = ENOMEM; + ret = -ENOMEM; goto exit_set_errno; } @@ -599,7 +599,6 @@ int eventfd(unsigned int count, int flags) if (ret < 0) { ferr("Failed to register new device %s: %d\n", devpath, ret); - ret = -ret; goto exit_release_minor; } @@ -614,7 +613,7 @@ int eventfd(unsigned int count, int flags) if (new_fd < 0) { - ret = -new_fd; + ret = new_fd; goto exit_unregister_driver; } @@ -626,6 +625,6 @@ exit_release_minor: eventfd_release_minor(new_dev->minor); eventfd_destroy(new_dev); exit_set_errno: - set_errno(ret); + set_errno(-ret); return ERROR; } diff --git a/fs/vfs/fs_statfs.c b/fs/vfs/fs_statfs.c index 94e5835bec..1d6050c2f7 100644 --- a/fs/vfs/fs_statfs.c +++ b/fs/vfs/fs_statfs.c @@ -80,13 +80,13 @@ int statfs(FAR const char *path, FAR struct statfs *buf) if (path == NULL || buf == NULL) { - ret = EFAULT; + ret = -EFAULT; goto errout; } if (*path == '\0') { - ret = ENOENT; + ret = -ENOENT; goto errout; } @@ -101,7 +101,6 @@ int statfs(FAR const char *path, FAR struct statfs *buf) * mountpoint that includes in this path. */ - ret = -ret; goto errout_with_search; } @@ -140,7 +139,6 @@ int statfs(FAR const char *path, FAR struct statfs *buf) if (ret < 0) { - ret = -ret; goto errout_with_inode; } @@ -159,6 +157,6 @@ errout_with_search: RELEASE_SEARCH(&desc); errout: - set_errno(ret); + set_errno(-ret); return ERROR; } diff --git a/fs/vfs/fs_timerfd.c b/fs/vfs/fs_timerfd.c new file mode 100644 index 0000000000..de90cb9d0d --- /dev/null +++ b/fs/vfs/fs_timerfd.c @@ -0,0 +1,822 @@ +/**************************************************************************** + * fs/vfs/fs_timerfd.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include "clock/clock.h" +#include "inode/inode.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_TIMER_FD_VFS_PATH +#define CONFIG_TIMER_FD_VFS_PATH "/var/timer" +#endif + +#ifndef CONFIG_TIMER_FD_NPOLLWAITERS +/* Maximum number of threads than can be waiting for POLL events */ +#define CONFIG_TIMER_FD_NPOLLWAITERS 2 +#endif + +#define TIMER_FD_WORK LPWORK + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct timerfd_waiter_sem_s +{ + sem_t sem; + struct timerfd_waiter_sem_s *next; +} timerfd_waiter_sem_t; + +/* This structure describes the internal state of the driver */ + +struct timerfd_priv_s +{ + sem_t exclsem; /* Enforces device exclusive access */ + timerfd_waiter_sem_t *rdsems; /* List of blocking readers */ + int clock; /* Clock to use as the timing base */ + int delay; /* If non-zero, used to reset repetitive + * timers */ + struct wdog_s wdog; /* The watchdog that provides the timing */ + struct work_s work; /* For deferred timeout operations */ + timerfd_t counter; /* timerfd counter */ + spinlock_t lock; /* timerfd counter specific lock */ + unsigned int minor; /* timerfd minor number */ + uint8_t crefs; /* References counts on timerfd (max: 255) */ + + /* The following is a list if poll structures of threads waiting for + * driver events. + */ + +#ifdef CONFIG_TIMER_FD_POLL + FAR struct pollfd *fds[CONFIG_TIMER_FD_NPOLLWAITERS]; +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int timerfd_open(FAR struct file *filep); +static int timerfd_close(FAR struct file *filep); + +static ssize_t timerfd_read(FAR struct file *filep, FAR char *buffer, + size_t len); +#ifdef CONFIG_TIMER_FD_POLL +static int timerfd_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); + +static void timerfd_pollnotify(FAR struct timerfd_priv_s *dev, + pollevent_t eventset); +#endif + +static int timerfd_blocking_io(FAR struct timerfd_priv_s *dev, + timerfd_waiter_sem_t *sem, + FAR timerfd_waiter_sem_t **slist); + +static unsigned int timerfd_get_unique_minor(void); +static void timerfd_release_minor(unsigned int minor); + +static FAR struct timerfd_priv_s *timerfd_allocdev(void); +static void timerfd_destroy(FAR struct timerfd_priv_s *dev); + +static void timerfd_timeout_work(FAR void *arg); +static void timerfd_timeout(wdparm_t idev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_timerfd_fops = +{ + timerfd_open, /* open */ + timerfd_close, /* close */ + timerfd_read, /* read */ + NULL, /* write */ + NULL, /* seek */ + NULL /* ioctl */ +#ifdef CONFIG_TIMER_FD_POLL + , timerfd_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static FAR struct timerfd_priv_s *timerfd_allocdev(void) +{ + FAR struct timerfd_priv_s *dev; + + dev = (FAR struct timerfd_priv_s *) + kmm_zalloc(sizeof(struct timerfd_priv_s)); + if (dev) + { + /* Initialize the private structure */ + + nxsem_init(&dev->exclsem, 0, 0); + } + + return dev; +} + +static void timerfd_destroy(FAR struct timerfd_priv_s *dev) +{ + wd_cancel(&dev->wdog); + work_cancel(TIMER_FD_WORK, &dev->work); + nxsem_destroy(&dev->exclsem); + kmm_free(dev); +} + +static timerfd_t timerfd_get_counter(FAR struct timerfd_priv_s *dev) +{ + timerfd_t counter; + irqstate_t intflags; + + intflags = spin_lock_irqsave(&dev->lock); + counter = dev->counter; + spin_unlock_irqrestore(&dev->lock, intflags); + + return counter; +} + +#ifdef CONFIG_TIMER_FD_POLL +static void timerfd_pollnotify(FAR struct timerfd_priv_s *dev, + pollevent_t eventset) +{ + FAR struct pollfd *fds; + int i; + + for (i = 0; i < CONFIG_TIMER_FD_NPOLLWAITERS; i++) + { + fds = dev->fds[i]; + if (fds) + { + fds->revents |= eventset & fds->events; + + if (fds->revents != 0) + { + nxsem_post(fds->sem); + } + } + } +} +#endif + +static unsigned int timerfd_get_unique_minor(void) +{ + static unsigned int minor; + + return minor++; +} + +static void timerfd_release_minor(unsigned int minor) +{ +} + +static int timerfd_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct timerfd_priv_s *priv = inode->i_private; + int ret; + + /* Get exclusive access to the device structures */ + + ret = nxsem_wait(&priv->exclsem); + if (ret < 0) + { + return ret; + } + + finfo("crefs: %d <%s>\n", priv->crefs, inode->i_name); + + if (priv->crefs >= 255) + { + /* More than 255 opens; uint8_t would overflow to zero */ + + ret = -EMFILE; + } + else + { + /* Save the new open count on success */ + + priv->crefs += 1; + ret = OK; + } + + nxsem_post(&priv->exclsem); + return ret; +} + +static int timerfd_close(FAR struct file *filep) +{ + int ret; + FAR struct inode *inode = filep->f_inode; + FAR struct timerfd_priv_s *priv = inode->i_private; + + /* devpath: TIMER_FD_VFS_PATH + /tfd (4) + %u (10) + null char (1) */ + + char devpath[sizeof(CONFIG_TIMER_FD_VFS_PATH) + 4 + 10 + 1]; + + /* Get exclusive access to the device structures */ + + ret = nxsem_wait(&priv->exclsem); + if (ret < 0) + { + return ret; + } + + finfo("crefs: %d <%s>\n", priv->crefs, inode->i_name); + + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. + */ + + if (priv->crefs > 1) + { + /* Just decrement the reference count and release the semaphore */ + + priv->crefs--; + nxsem_post(&priv->exclsem); + return OK; + } + + /* Re-create the path to the driver. */ + + finfo("destroy\n"); + sprintf(devpath, CONFIG_TIMER_FD_VFS_PATH "/tfd%u", priv->minor); + + /* Will be unregistered later after close is done */ + + unregister_driver(devpath); + + DEBUGASSERT(priv->exclsem.semcount == 0); + timerfd_release_minor(priv->minor); + timerfd_destroy(priv); + + return OK; +} + +static int timerfd_blocking_io(FAR struct timerfd_priv_s *dev, + timerfd_waiter_sem_t *sem, + FAR timerfd_waiter_sem_t **slist) +{ + int ret; + sem->next = *slist; + *slist = sem; + + nxsem_post(&dev->exclsem); + + /* Wait for timerfd to notify */ + + ret = nxsem_wait(&sem->sem); + + if (ret < 0) + { + /* Interrupted wait, unregister semaphore + * TODO ensure that exclsem wait does not fail (ECANCELED) + */ + + nxsem_wait_uninterruptible(&dev->exclsem); + + timerfd_waiter_sem_t *cur_sem = *slist; + + if (cur_sem == sem) + { + *slist = sem->next; + } + else + { + while (cur_sem) + { + if (cur_sem->next == sem) + { + cur_sem->next = sem->next; + break; + } + } + } + + nxsem_post(&dev->exclsem); + return ret; + } + + return nxsem_wait(&dev->exclsem); +} + +static ssize_t timerfd_read(FAR struct file *filep, FAR char *buffer, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct timerfd_priv_s *dev = inode->i_private; + irqstate_t intflags; + ssize_t ret; + + if (len < sizeof(timerfd_t) || buffer == NULL) + { + return -EINVAL; + } + + ret = nxsem_wait(&dev->exclsem); + if (ret < 0) + { + return ret; + } + + /* Wait for an incoming event */ + + if (timerfd_get_counter(dev) == 0) + { + if (filep->f_oflags & O_NONBLOCK) + { + nxsem_post(&dev->exclsem); + return -EAGAIN; + } + + timerfd_waiter_sem_t sem; + nxsem_init(&sem.sem, 0, 0); + nxsem_set_protocol(&sem.sem, SEM_PRIO_NONE); + + do + { + ret = timerfd_blocking_io(dev, &sem, &dev->rdsems); + if (ret < 0) + { + nxsem_destroy(&sem.sem); + return ret; + } + } + while (timerfd_get_counter(dev) == 0); + + nxsem_destroy(&sem.sem); + } + + /* Device ready for read. Ensure that interrupts are disabled and we + * do not lose counts if expiration occurs after read, but before setting + * counter to zero + */ + + intflags = spin_lock_irqsave(&dev->lock); + + *(FAR timerfd_t *)buffer = dev->counter; + dev->counter = 0; + + spin_unlock_irqrestore(&dev->lock, intflags); + + nxsem_post(&dev->exclsem); + return sizeof(timerfd_t); +} + +#ifdef CONFIG_TIMER_FD_POLL +static int timerfd_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct timerfd_priv_s *dev = inode->i_private; + int ret; + int i; + + ret = nxsem_wait(&dev->exclsem); + if (ret < 0) + { + return ret; + } + + ret = OK; + + if (!setup) + { + /* This is a request to tear down the poll. */ + + FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv; + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + goto out; + } + + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_TIMER_FD_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!dev->fds[i]) + { + /* Bind the poll structure and this slot */ + + dev->fds[i] = fds; + fds->priv = &dev->fds[i]; + break; + } + } + + if (i >= CONFIG_TIMER_FD_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto out; + } + + /* Notify the POLLIN event if the counter is not zero */ + + if (timerfd_get_counter(dev) > 0) + { + timerfd_pollnotify(dev, POLLIN); + } + +out: + nxsem_post(&dev->exclsem); + return ret; +} +#endif + +static void timerfd_timeout_work(FAR void *arg) +{ + FAR struct timerfd_priv_s *dev = (FAR struct timerfd_priv_s *)arg; + int ret; + + ret = nxsem_wait_uninterruptible(&dev->exclsem); + if (ret < 0) + { + wd_cancel(&dev->wdog); + return; + } + +#ifdef CONFIG_TIMER_FD_POLL + /* Notify all poll/select waiters */ + + timerfd_pollnotify(dev, POLLIN); +#endif + + /* Notify all of the waiting readers */ + + timerfd_waiter_sem_t *cur_sem = dev->rdsems; + while (cur_sem != NULL) + { + nxsem_post(&cur_sem->sem); + cur_sem = cur_sem->next; + } + + dev->rdsems = NULL; + + nxsem_post(&dev->exclsem); +} + +static void timerfd_timeout(wdparm_t idev) +{ + FAR struct timerfd_priv_s *dev = (FAR struct timerfd_priv_s *)idev; + irqstate_t intflags; + + /* Disable interrupts to ensure that expiration counter is accessed + * atomically + */ + + intflags = spin_lock_irqsave(&dev->lock); + + /* Increment timer expiration counter */ + + dev->counter++; + + work_queue(TIMER_FD_WORK, &dev->work, timerfd_timeout_work, dev, 0); + + /* If this is a repetitive timer, then restart the watchdog */ + + if (dev->delay) + { + wd_start(&dev->wdog, dev->delay, timerfd_timeout, idev); + } + + spin_unlock_irqrestore(&dev->lock, intflags); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int timerfd_create(int clockid, int flags) +{ + FAR struct timerfd_priv_s *new_dev; + int new_fd; + int ret; + + /* Sanity checks. */ + + if (clockid != CLOCK_REALTIME +#ifdef CONFIG_CLOCK_MONOTONIC + && clockid != CLOCK_MONOTONIC + && clockid != CLOCK_BOOTTIME +#endif /* CONFIG_CLOCK_MONOTONIC */ + ) + { + ret = -EINVAL; + goto errout; + } + + /* devpath: TIMER_FD_VFS_PATH + /tfd (4) + %u (10) + null char (1) */ + + char devpath[sizeof(CONFIG_TIMER_FD_VFS_PATH) + 4 + 10 + 1]; + + /* Allocate instance data for this driver */ + + new_dev = timerfd_allocdev(); + if (new_dev == NULL) + { + /* Failed to allocate new device */ + + ret = -ENOMEM; + goto errout; + } + + /* Initialize the timer instance */ + + new_dev->clock = clockid; + new_dev->crefs = 1; + new_dev->delay = 0; + new_dev->counter = 0; + + /* Request a unique minor device number */ + + new_dev->minor = timerfd_get_unique_minor(); + + /* Get device path */ + + sprintf(devpath, CONFIG_TIMER_FD_VFS_PATH "/tfd%u", new_dev->minor); + + /* Register the driver */ + + ret = register_driver(devpath, &g_timerfd_fops, 0444, new_dev); + if (ret < 0) + { + ferr("Failed to register new device %s: %d\n", devpath, ret); + ret = -ENODEV; + goto errout_release_minor; + } + + /* Device is ready for use */ + + nxsem_post(&new_dev->exclsem); + + /* Try open new device */ + + new_fd = nx_open(devpath, O_RDONLY | + (flags & (TFD_NONBLOCK | TFD_CLOEXEC))); + + if (new_fd < 0) + { + ret = new_fd; + goto errout_unregister_driver; + } + + return new_fd; + +errout_unregister_driver: + unregister_driver(devpath); +errout_release_minor: + timerfd_release_minor(new_dev->minor); + timerfd_destroy(new_dev); +errout: + set_errno(-ret); + return ERROR; +} + +int timerfd_settime(int fd, int flags, + FAR const struct itimerspec *new_value, + FAR struct itimerspec *old_value) +{ + FAR struct file *filep; + FAR struct timerfd_priv_s *dev; + irqstate_t intflags; + sclock_t delay; + int ret; + + /* Some sanity checks */ + + if (!new_value) + { + ret = -EFAULT; + goto errout; + } + + if (flags && (flags & TFD_TIMER_ABSTIME) == 0) + { + ret = -EINVAL; + goto errout; + } + + /* Get file pointer by file descriptor */ + + ret = fs_getfilep(fd, &filep); + if (ret < 0) + { + goto errout; + } + + /* Check fd come from us */ + + if (!filep->f_inode || filep->f_inode->u.i_ops != &g_timerfd_fops) + { + ret = -EINVAL; + goto errout; + } + + dev = (FAR struct timerfd_priv_s *)filep->f_inode->i_private; + + if (old_value) + { + /* Get the number of ticks before the underlying watchdog expires */ + + delay = wd_gettime(&dev->wdog); + + /* Convert that to a struct timespec and return it */ + + clock_ticks2time(delay, &old_value->it_value); + clock_ticks2time(dev->delay, &old_value->it_interval); + } + + /* Disable interrupts here to ensure that expiration counter is accessed + * atomicaly and timeout work is canceled with the same sequence + */ + + intflags = spin_lock_irqsave(&dev->lock); + + /* Disarm the timer (in case the timer was already armed when + * timerfd_settime() is called). + */ + + wd_cancel(&dev->wdog); + + /* Cancel notification work */ + + work_cancel(TIMER_FD_WORK, &dev->work); + + /* Clear expiration counter */ + + dev->counter = 0; + + /* If the it_value member of value is zero, the timer will not be + * re-armed + */ + + if (new_value->it_value.tv_sec <= 0 && new_value->it_value.tv_nsec <= 0) + { + spin_unlock_irqrestore(NULL, intflags); + return OK; + } + + /* Setup up any repetitive timer */ + + if (new_value->it_interval.tv_sec > 0 || + new_value->it_interval.tv_nsec > 0) + { + clock_time2ticks(&new_value->it_interval, &delay); + + /* REVISIT: Should delay be sclock_t? */ + + dev->delay = (int)delay; + } + else + { + dev->delay = 0; + } + + /* We need to disable timer interrupts through the following section so + * that the system timer is stable. + */ + + /* Check if abstime is selected */ + + if ((flags & TFD_TIMER_ABSTIME) != 0) + { + /* Calculate a delay corresponding to the absolute time in 'value' */ + + clock_abstime2ticks(dev->clock, &new_value->it_value, &delay); + } + else + { + /* Calculate a delay assuming that 'value' holds the relative time + * to wait. We have internal knowledge that clock_time2ticks always + * returns success. + */ + + clock_time2ticks(&new_value->it_value, &delay); + } + + /* If the time is in the past or now, then set up the next interval + * instead (assuming a repetitive timer). + */ + + if (delay <= 0) + { + delay = dev->delay; + } + + /* Then start the watchdog */ + + if (delay > 0) + { + ret = wd_start(&dev->wdog, delay, timerfd_timeout, (wdparm_t)dev); + if (ret < 0) + { + spin_unlock_irqrestore(&dev->lock, intflags); + goto errout; + } + } + + spin_unlock_irqrestore(&dev->lock, intflags); + return OK; + +errout: + set_errno(-ret); + return ERROR; +} + +int timerfd_gettime(int fd, FAR struct itimerspec *curr_value) +{ + FAR struct file *filep; + FAR struct timerfd_priv_s *dev; + sclock_t ticks; + int ret; + + /* Some sanity checks */ + + if (!curr_value) + { + ret = -EFAULT; + goto errout; + } + + /* Get file pointer by file descriptor */ + + ret = fs_getfilep(fd, &filep); + if (ret < 0) + { + goto errout; + } + + /* Check fd come from us */ + + if (!filep->f_inode || filep->f_inode->u.i_ops != &g_timerfd_fops) + { + ret = -EINVAL; + goto errout; + } + + dev = (FAR struct timerfd_priv_s *)filep->f_inode->i_private; + + /* Get the number of ticks before the underlying watchdog expires */ + + ticks = wd_gettime(&dev->wdog); + + /* Convert that to a struct timespec and return it */ + + clock_ticks2time(ticks, &curr_value->it_value); + clock_ticks2time(dev->delay, &curr_value->it_interval); + return OK; + +errout: + set_errno(-ret); + return ERROR; +} diff --git a/include/nuttx/wdog.h b/include/nuttx/wdog.h index 30a6256a04..9df0c5337e 100644 --- a/include/nuttx/wdog.h +++ b/include/nuttx/wdog.h @@ -90,7 +90,7 @@ extern "C" * Name: wd_start * * Description: - * This function adds a watchdog timer to the actuve timer queue. The + * This function adds a watchdog timer to the active timer queue. The * specified watchdog function at 'wdentry' will be called from the * interrupt level after the specified number of ticks has elapsed. * Watchdog timers may be started from the interrupt level. diff --git a/include/sys/syscall_lookup.h b/include/sys/syscall_lookup.h index ebd5e0e174..8b4e0a17be 100644 --- a/include/sys/syscall_lookup.h +++ b/include/sys/syscall_lookup.h @@ -218,6 +218,11 @@ SYSCALL_LOOKUP(pwrite, 4) #ifdef CONFIG_EVENT_FD SYSCALL_LOOKUP(eventfd, 2) #endif +#ifdef CONFIG_TIMER_FD + SYSCALL_LOOKUP(timerfd_create, 2) + SYSCALL_LOOKUP(timerfd_settime, 4) + SYSCALL_LOOKUP(timerfd_gettime, 2) +#endif #ifdef CONFIG_SERIAL_TERMIOS SYSCALL_LOOKUP(tcdrain, 1) #endif diff --git a/include/sys/timerfd.h b/include/sys/timerfd.h new file mode 100644 index 0000000000..f3a4ff5c26 --- /dev/null +++ b/include/sys/timerfd.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * include/sys/timerfd.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_SYS_TIMERFD_H +#define __INCLUDE_SYS_TIMERFD_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define TFD_NONBLOCK O_NONBLOCK +#define TFD_CLOEXEC O_CLOEXEC + +#define TFD_TIMER_ABSTIME TIMER_ABSTIME + +/**************************************************************************** + * Public Type Declarations + ****************************************************************************/ + +/* Type for timer counter */ + +/* Type for event counter */ + +#ifdef __INT64_DEFINED +typedef uint64_t timerfd_t; +#else +typedef uint32_t timerfd_t; +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +int timerfd_create(int clockid, int flags); + +int timerfd_settime(int fd, int flags, + FAR const struct itimerspec *new_value, + FAR struct itimerspec *old_value); + +int timerfd_gettime(int fd, FAR struct itimerspec *curr_value); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_SYS_TIMERFD_H */ diff --git a/libs/libc/stdio/lib_getdelim.c b/libs/libc/stdio/lib_getdelim.c index 2704955f1e..6a742317c9 100644 --- a/libs/libc/stdio/lib_getdelim.c +++ b/libs/libc/stdio/lib_getdelim.c @@ -173,7 +173,7 @@ ssize_t getdelim(FAR char **lineptr, size_t *n, int delimiter, newbuffer = (FAR char *)lib_realloc(*lineptr, bufsize); if (newbuffer == NULL) { - ret = -ENOMEM; + ret = ENOMEM; goto errout; } diff --git a/syscall/syscall.csv b/syscall/syscall.csv index 3c8daf70ff..fd53acb31f 100644 --- a/syscall/syscall.csv +++ b/syscall/syscall.csv @@ -184,6 +184,9 @@ "timer_getoverrun","time.h","!defined(CONFIG_DISABLE_POSIX_TIMERS)","int","timer_t" "timer_gettime","time.h","!defined(CONFIG_DISABLE_POSIX_TIMERS)","int","timer_t","FAR struct itimerspec *" "timer_settime","time.h","!defined(CONFIG_DISABLE_POSIX_TIMERS)","int","timer_t","int","FAR const struct itimerspec *","FAR struct itimerspec *" +"timerfd_create","sys/timerfd.h","defined(CONFIG_TIMER_FD)","int","int","int" +"timerfd_gettime","sys/timerfd.h","defined(CONFIG_TIMER_FD)","int","int","FAR struct itimerspec *" +"timerfd_settime","sys/timerfd.h","defined(CONFIG_TIMER_FD)","int","int","int","FAR const struct itimerspec *","FAR const struct itimerspec *" "umount2","sys/mount.h","!defined(CONFIG_DISABLE_MOUNTPOINT)","int","FAR const char *","unsigned int" "unlink","unistd.h","!defined(CONFIG_DISABLE_MOUNTPOINT)","int","FAR const char *" "unsetenv","stdlib.h","!defined(CONFIG_DISABLE_ENVIRON)","int","FAR const char *"