timerfd: Allow timers to be cancelled when clock was set
[firefly-linux-kernel-4.4.55.git] / fs / timerfd.c
index 8c4fc1425b3eff171bd37b9e1bed290b93473e11..7e14c9e7c4ee97b8a15700d463846777a9592ac8 100644 (file)
 struct timerfd_ctx {
        struct hrtimer tmr;
        ktime_t tintv;
+       ktime_t moffs;
        wait_queue_head_t wqh;
        u64 ticks;
        int expired;
        int clockid;
+       bool might_cancel;
 };
 
 /*
@@ -59,24 +61,52 @@ static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx)
        return remaining.tv64 < 0 ? ktime_set(0, 0): remaining;
 }
 
-static void timerfd_setup(struct timerfd_ctx *ctx, int flags,
-                         const struct itimerspec *ktmr)
+static bool timerfd_canceled(struct timerfd_ctx *ctx)
+{
+       ktime_t moffs;
+
+       if (!ctx->might_cancel)
+               return false;
+
+       moffs = ktime_get_monotonic_offset();
+
+       if (moffs.tv64 == ctx->moffs.tv64)
+               return false;
+
+       ctx->moffs = moffs;
+       return true;
+}
+
+static int timerfd_setup(struct timerfd_ctx *ctx, int flags,
+                        const struct itimerspec *ktmr)
 {
        enum hrtimer_mode htmode;
        ktime_t texp;
+       int clockid = ctx->clockid;
 
        htmode = (flags & TFD_TIMER_ABSTIME) ?
                HRTIMER_MODE_ABS: HRTIMER_MODE_REL;
 
+       ctx->might_cancel = false;
+       if (htmode == HRTIMER_MODE_ABS && ctx->clockid == CLOCK_REALTIME &&
+           (flags & TFD_TIMER_CANCELON_SET)) {
+               clockid = CLOCK_REALTIME_COS;
+               ctx->might_cancel = true;
+       }
+
        texp = timespec_to_ktime(ktmr->it_value);
        ctx->expired = 0;
        ctx->ticks = 0;
        ctx->tintv = timespec_to_ktime(ktmr->it_interval);
-       hrtimer_init(&ctx->tmr, ctx->clockid, htmode);
+       hrtimer_init(&ctx->tmr, clockid, htmode);
        hrtimer_set_expires(&ctx->tmr, texp);
        ctx->tmr.function = timerfd_tmrproc;
-       if (texp.tv64 != 0)
+       if (texp.tv64 != 0) {
                hrtimer_start(&ctx->tmr, texp, htmode);
+               if (timerfd_canceled(ctx))
+                       return -ECANCELED;
+       }
+       return 0;
 }
 
 static int timerfd_release(struct inode *inode, struct file *file)
@@ -118,8 +148,21 @@ static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count,
                res = -EAGAIN;
        else
                res = wait_event_interruptible_locked_irq(ctx->wqh, ctx->ticks);
+
        if (ctx->ticks) {
                ticks = ctx->ticks;
+
+               /*
+                * If clock has changed, we do not care about the
+                * ticks and we do not rearm the timer. Userspace must
+                * reevaluate anyway.
+                */
+               if (timerfd_canceled(ctx)) {
+                       ticks = 0;
+                       ctx->expired = 0;
+                       res = -ECANCELED;
+               }
+
                if (ctx->expired && ctx->tintv.tv64) {
                        /*
                         * If tintv.tv64 != 0, this is a periodic timer that
@@ -183,6 +226,7 @@ SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags)
        init_waitqueue_head(&ctx->wqh);
        ctx->clockid = clockid;
        hrtimer_init(&ctx->tmr, clockid, HRTIMER_MODE_ABS);
+       ctx->moffs = ktime_get_monotonic_offset();
 
        ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx,
                               O_RDWR | (flags & TFD_SHARED_FCNTL_FLAGS));
@@ -199,6 +243,7 @@ SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags,
        struct file *file;
        struct timerfd_ctx *ctx;
        struct itimerspec ktmr, kotmr;
+       int ret;
 
        if (copy_from_user(&ktmr, utmr, sizeof(ktmr)))
                return -EFAULT;
@@ -240,14 +285,14 @@ SYSCALL_DEFINE4(timerfd_settime, int, ufd, int, flags,
        /*
         * Re-program the timer to the new value ...
         */
-       timerfd_setup(ctx, flags, &ktmr);
+       ret = timerfd_setup(ctx, flags, &ktmr);
 
        spin_unlock_irq(&ctx->wqh.lock);
        fput(file);
        if (otmr && copy_to_user(otmr, &kotmr, sizeof(kotmr)))
                return -EFAULT;
 
-       return 0;
+       return ret;
 }
 
 SYSCALL_DEFINE2(timerfd_gettime, int, ufd, struct itimerspec __user *, otmr)