Per file descriptor high-resolution timers. A classic unix file interface for the POSIX timer_(create|settime|gettime|delete) family of functions. Signed-off-by: Davi E. M. Arnaut --- fs/pollfs/Makefile | 1 fs/pollfs/timer.c | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++ init/Kconfig | 7 + 3 files changed, 206 insertions(+) Index: linux-2.6/fs/pollfs/timer.c =================================================================== --- /dev/null +++ linux-2.6/fs/pollfs/timer.c @@ -0,0 +1,198 @@ +/* + * pollable timers + * + * Copyright (C) 2007 Davi E. M. Arnaut + * + * Licensed under the GNU GPL. See the file COPYING for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pfs_timer { + wait_queue_head_t wait; + ktime_t interval; + spinlock_t lock; + unsigned long overruns; + struct hrtimer timer; + struct pfs_file file; +}; + +struct hrtimerspec { + int flags; + clockid_t clock; + struct itimerspec expr; +}; + +static ssize_t read(struct pfs_timer *evs, struct itimerspec __user *uspec) +{ + int ret = -EAGAIN; + ktime_t remaining = {}; + unsigned long overruns = 0; + struct itimerspec spec = {}; + struct hrtimer *timer = &evs->timer; + + spin_lock_irq(&evs->lock); + + if (!evs->overruns) + goto out_unlock; + + if (hrtimer_active(timer)) + remaining = hrtimer_get_remaining(timer); + else if (evs->interval.tv64 > 0) + overruns = hrtimer_forward(timer, hrtimer_cb_get_time(timer), + evs->interval); + + ret = -EOVERFLOW; + if (overruns > (ULONG_MAX - evs->overruns)) + goto out_unlock; + else + evs->overruns += overruns; + + if (remaining.tv64 > 0) + spec.it_value = ktime_to_timespec(remaining); + + spec.it_interval = ktime_to_timespec(evs->interval); + + ret = 0; + +out_unlock: + spin_unlock_irq(&evs->lock); + + if (ret) + return ret; + + if (copy_to_user(uspec, &spec, sizeof(spec))) + return -EFAULT; + + return 0; +} + +static enum hrtimer_restart timer_fn(struct hrtimer *timer) +{ + struct pfs_timer *evs = container_of(timer, struct pfs_timer, timer); + unsigned long flags; + + spin_lock_irqsave(&evs->lock, flags); + /* timer tick, interval has elapsed */ + if (!evs->overruns++) + wake_up_all(&evs->wait); + spin_unlock_irqrestore(&evs->lock, flags); + + return HRTIMER_NORESTART; +} + +static inline void rearm_timer(struct pfs_timer *evs, struct hrtimerspec *spec) +{ + struct hrtimer *timer = &evs->timer; + enum hrtimer_mode mode = HRTIMER_MODE_REL; + + if (spec->flags & TIMER_ABSTIME) + mode = HRTIMER_MODE_ABS; + + do { + spin_lock_irq(&evs->lock); + if (hrtimer_try_to_cancel(timer) >= 0) + break; + spin_unlock_irq(&evs->lock); + cpu_relax(); + } while (1); + + hrtimer_init(timer, spec->clock, mode); + + timer->function = timer_fn; + timer->expires = timespec_to_ktime(spec->expr.it_value); + evs->interval = timespec_to_ktime(spec->expr.it_interval); + + if (timer->expires.tv64) + hrtimer_start(timer, timer->expires, mode); + + spin_unlock_irq(&evs->lock); +} + +static inline int spec_invalid(const struct hrtimerspec *spec) +{ + if (spec->clock != CLOCK_REALTIME && spec->clock != CLOCK_MONOTONIC) + return 1; + + if (!timespec_valid(&spec->expr.it_value) || + !timespec_valid(&spec->expr.it_interval)) + return 1; + + return 0; +} + +static ssize_t write(struct pfs_timer *evs, + const struct hrtimerspec __user *uspec) +{ + struct hrtimerspec spec; + + if (copy_from_user(&spec, uspec, sizeof(spec))) + return -EFAULT; + + if (spec_invalid(&spec)) + return -EINVAL; + + rearm_timer(evs, &spec); + + return 0; +} + +static int poll(struct pfs_timer *evs) +{ + int ret; + + ret = evs->overruns ? POLLIN : 0; + + return ret; +} + +static int release(struct pfs_timer *evs) +{ + hrtimer_cancel(&evs->timer); + kfree(evs); + + return 0; +} + +static const struct pfs_operations timer_ops = { + .read = PFS_READ(read, struct pfs_timer, struct itimerspec), + .write = PFS_WRITE(write, struct pfs_timer, struct hrtimerspec), + .poll = PFS_POLL(poll, struct pfs_timer), + .release = PFS_RELEASE(release, struct pfs_timer), + .rsize = sizeof(struct itimerspec), + .wsize = sizeof(struct hrtimerspec), +}; + +asmlinkage long sys_pltimer(void) +{ + long error; + struct pfs_timer *evs; + + evs = kmalloc(sizeof(*evs), GFP_KERNEL); + if (!evs) + return -ENOMEM; + + evs->overruns = 0; + spin_lock_init(&evs->lock); + init_waitqueue_head(&evs->wait); + hrtimer_init(&evs->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + + evs->file.data = evs; + evs->file.fops = &timer_ops; + evs->file.wait = &evs->wait; + + error = pfs_open(&evs->file); + + if (error < 0) + release(evs); + + return error; +} Index: linux-2.6/fs/pollfs/Makefile =================================================================== --- linux-2.6.orig/fs/pollfs/Makefile +++ linux-2.6/fs/pollfs/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_POLLFS) += pollfs.o pollfs-y := file.o pollfs-$(CONFIG_POLLFS_SIGNAL) += signal.o +pollfs-$(CONFIG_POLLFS_TIMER) += timer.o Index: linux-2.6/init/Kconfig =================================================================== --- linux-2.6.orig/init/Kconfig +++ linux-2.6/init/Kconfig @@ -476,6 +476,13 @@ config POLLFS_SIGNAL help Pollable signal support +config POLLFS_TIMER + bool "Enable pollfs timer" if EMBEDDED + default y + depends on POLLFS + help + Pollable timer support + config SHMEM bool "Use full shmem filesystem" if EMBEDDED default y -- - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/