linux/fs/timerfd.c
<<
>>
Prefs
   1/*
   2 *  fs/timerfd.c
   3 *
   4 *  Copyright (C) 2007  Davide Libenzi <davidel@xmailserver.org>
   5 *
   6 *
   7 *  Thanks to Thomas Gleixner for code reviews and useful comments.
   8 *
   9 */
  10
  11#include <linux/file.h>
  12#include <linux/poll.h>
  13#include <linux/init.h>
  14#include <linux/fs.h>
  15#include <linux/sched.h>
  16#include <linux/kernel.h>
  17#include <linux/list.h>
  18#include <linux/spinlock.h>
  19#include <linux/time.h>
  20#include <linux/hrtimer.h>
  21#include <linux/anon_inodes.h>
  22#include <linux/timerfd.h>
  23#include <linux/syscalls.h>
  24
  25struct timerfd_ctx {
  26        struct hrtimer tmr;
  27        ktime_t tintv;
  28        wait_queue_head_t wqh;
  29        u64 ticks;
  30        int expired;
  31        int clockid;
  32};
  33
  34/*
  35 * This gets called when the timer event triggers. We set the "expired"
  36 * flag, but we do not re-arm the timer (in case it's necessary,
  37 * tintv.tv64 != 0) until the timer is accessed.
  38 */
  39static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr)
  40{
  41        struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, tmr);
  42        unsigned long flags;
  43
  44        spin_lock_irqsave(&ctx->wqh.lock, flags);
  45        ctx->expired = 1;
  46        ctx->ticks++;
  47        wake_up_locked(&ctx->wqh);
  48        spin_unlock_irqrestore(&ctx->wqh.lock, flags);
  49
  50        return HRTIMER_NORESTART;
  51}
  52
  53static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx)
  54{
  55        ktime_t now, remaining;
  56
  57        now = ctx->tmr.base->get_time();
  58        remaining = ktime_sub(ctx->tmr.expires, now);
  59
  60        return remaining.tv64 < 0 ? ktime_set(0, 0): remaining;
  61}
  62
  63static void timerfd_setup(struct timerfd_ctx *ctx, int flags,
  64                          const struct itimerspec *ktmr)
  65{
  66        enum hrtimer_mode htmode;
  67        ktime_t texp;
  68
  69        htmode = (flags & TFD_TIMER_ABSTIME) ?
  70                HRTIMER_MODE_ABS: HRTIMER_MODE_REL;
  71
  72        texp = timespec_to_ktime(ktmr->it_value);
  73        ctx->expired = 0;
  74        ctx->ticks = 0;
  75        ctx->tintv = timespec_to_ktime(ktmr->it_interval);
  76        hrtimer_init(&ctx->tmr, ctx->clockid, htmode);
  77        ctx->tmr.expires = texp;
  78        ctx->tmr.function = timerfd_tmrproc;
  79        if (texp.tv64 != 0)
  80                hrtimer_start(&ctx->tmr, texp, htmode);
  81}
  82
  83static int timerfd_release(struct inode *inode, struct file *file)
  84{
  85        struct timerfd_ctx *ctx = file->private_data;
  86
  87        hrtimer_cancel(&ctx->tmr);
  88        kfree(ctx);
  89        return 0;
  90}
  91
  92static unsigned int timerfd_poll(struct file *file, poll_table *wait)
  93{
  94        struct timerfd_ctx *ctx = file->private_data;
  95        unsigned int events = 0;
  96        unsigned long flags;
  97
  98        poll_wait(file, &ctx->wqh, wait);
  99
 100        spin_lock_irqsave(&ctx->wqh.lock, flags);
 101        if (ctx->ticks)
 102                events |= POLLIN;
 103        spin_unlock_irqrestore(&ctx->wqh.lock, flags);
 104
 105        return events;
 106}
 107
 108static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count,
 109                            loff_t *ppos)
 110{
 111        struct timerfd_ctx *ctx = file->private_data;
 112        ssize_t res;
 113        u64 ticks = 0;
 114        DECLARE_WAITQUEUE(wait, current);
 115
 116        if (count < sizeof(ticks))
 117                return -EINVAL;
 118        spin_lock_irq(&ctx->wqh.lock);
 119        res = -EAGAIN;
 120        if (!ctx->ticks && !(file->f_flags & O_NONBLOCK)) {
 121                __add_wait_queue(&ctx->wqh, &wait);
 122                for (res = 0;;) {
 123                        set_current_state(TASK_INTERRUPTIBLE);
 124                        if (ctx->ticks) {
 125                                res = 0;
 126                                break;
 127                        }
 128                        if (signal_pending(current)) {
 129                                res = -ERESTARTSYS;
 130                                break;
 131                        }
 132                        spin_unlock_irq(&ctx->wqh.lock);
 133                        schedule();
 134                        spin_lock_irq(&ctx->wqh.lock);
 135                }
 136                __remove_wait_queue(&ctx->wqh, &wait);
 137                __set_current_state(TASK_RUNNING);
 138        }
 139        if (ctx->ticks) {
 140                ticks = ctx->ticks;
 141                if (ctx->expired && ctx->tintv.tv64) {
 142                        /*
 143                         * If tintv.tv64 != 0, this is a periodic timer that
 144                         * needs to be re-armed. We avoid doing it in the timer
 145                         * callback to avoid DoS attacks specifying a very
 146                         * short timer period.
 147                         */
 148                        ticks += hrtimer_forward_now(&ctx->tmr,
 149                                                     ctx->tintv) - 1;
 150                        hrtimer_restart(&ctx->tmr);
 151                }
 152                ctx->expired = 0;
 153                ctx->ticks = 0;
 154        }
 155        spin_unlock_irq(&ctx->wqh.lock);
 156        if (ticks)
 157                res = put_user(ticks, (u64 __user *) buf) ? -EFAULT: sizeof(ticks);
 158        return res;
 159}
 160
 161static const struct file_operations timerfd_fops = {
 162        .release        = timerfd_release,
 163        .poll           = timerfd_poll,
 164        .read           = timerfd_read,
 165};
 166
 167static struct file *timerfd_fget(int fd)
 168{
 169        struct file *file;
 170
 171        file = fget(fd);
 172        if (!file)
 173                return ERR_PTR(-EBADF);
 174        if (file->f_op != &timerfd_fops) {
 175                fput(file);
 176                return ERR_PTR(-EINVAL);
 177        }
 178
 179        return file;
 180}
 181
 182SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags)
 183{
 184        int ufd;
 185        struct timerfd_ctx *ctx;
 186
 187        /* Check the TFD_* constants for consistency.  */
 188        BUILD_BUG_ON(TFD_CLOEXEC != O_CLOEXEC);
 189        BUILD_BUG_ON(TFD_NONBLOCK != O_NONBLOCK);
 190
 191        if ((flags & ~TFD_CREATE_FLAGS) ||
 192            (clockid != CLOCK_MONOTONIC &&
 193             clockid != CLOCK_REALTIME))
 194                return -EINVAL;
 195
 196        ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
 197        if (!ctx)
 198                return -ENOMEM;
 199
 200        init_waitqueue_head(&ctx->wqh);
 201        ctx->clockid = clockid;
 202        hrtimer_init(&ctx->tmr, clockid, HRTIMER_MODE_ABS);
 203
 204        ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx,
 205                               flags & TFD_SHARED_FCNTL_FLAGS);
 206        if (ufd < 0)
 207                kfree(ctx);
 208
 209        return ufd;
 210}
 211
 212SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags,
 213                const struct itimerspec __user *, utmr,
 214                struct itimerspec __user *, otmr)
 215{
 216        struct file *file;
 217        struct timerfd_ctx *ctx;
 218        struct itimerspec ktmr, kotmr;
 219
 220        if (copy_from_user(&ktmr, utmr, sizeof(ktmr)))
 221                return -EFAULT;
 222
 223        if ((flags & ~TFD_SETTIME_FLAGS) ||
 224            !timespec_valid(&ktmr.it_value) ||
 225            !timespec_valid(&ktmr.it_interval))
 226                return -EINVAL;
 227
 228        file = timerfd_fget(ufd);
 229        if (IS_ERR(file))
 230                return PTR_ERR(file);
 231        ctx = file->private_data;
 232
 233        /*
 234         * We need to stop the existing timer before reprogramming
 235         * it to the new values.
 236         */
 237        for (;;) {
 238                spin_lock_irq(&ctx->wqh.lock);
 239                if (hrtimer_try_to_cancel(&ctx->tmr) >= 0)
 240                        break;
 241                spin_unlock_irq(&ctx->wqh.lock);
 242                cpu_relax();
 243        }
 244
 245        /*
 246         * If the timer is expired and it's periodic, we need to advance it
 247         * because the caller may want to know the previous expiration time.
 248         * We do not update "ticks" and "expired" since the timer will be
 249         * re-programmed again in the following timerfd_setup() call.
 250         */
 251        if (ctx->expired && ctx->tintv.tv64)
 252                hrtimer_forward_now(&ctx->tmr, ctx->tintv);
 253
 254        kotmr.it_value = ktime_to_timespec(timerfd_get_remaining(ctx));
 255        kotmr.it_interval = ktime_to_timespec(ctx->tintv);
 256
 257        /*
 258         * Re-program the timer to the new value ...
 259         */
 260        timerfd_setup(ctx, flags, &ktmr);
 261
 262        spin_unlock_irq(&ctx->wqh.lock);
 263        fput(file);
 264        if (otmr && copy_to_user(otmr, &kotmr, sizeof(kotmr)))
 265                return -EFAULT;
 266
 267        return 0;
 268}
 269
 270SYSCALL_DEFINE2(timerfd_gettime, int, ufd, struct itimerspec __user *, otmr)
 271{
 272        struct file *file;
 273        struct timerfd_ctx *ctx;
 274        struct itimerspec kotmr;
 275
 276        file = timerfd_fget(ufd);
 277        if (IS_ERR(file))
 278                return PTR_ERR(file);
 279        ctx = file->private_data;
 280
 281        spin_lock_irq(&ctx->wqh.lock);
 282        if (ctx->expired && ctx->tintv.tv64) {
 283                ctx->expired = 0;
 284                ctx->ticks +=
 285                        hrtimer_forward_now(&ctx->tmr, ctx->tintv) - 1;
 286                hrtimer_restart(&ctx->tmr);
 287        }
 288        kotmr.it_value = ktime_to_timespec(timerfd_get_remaining(ctx));
 289        kotmr.it_interval = ktime_to_timespec(ctx->tintv);
 290        spin_unlock_irq(&ctx->wqh.lock);
 291        fput(file);
 292
 293        return copy_to_user(otmr, &kotmr, sizeof(kotmr)) ? -EFAULT: 0;
 294}
 295
 296
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.