]> Pileus Git - ~andy/linux/blobdiff - kernel/time/timekeeping.c
time: Fix timeekeping_get_ns overflow on 32bit systems
[~andy/linux] / kernel / time / timekeeping.c
index e16af197a2bc54c8f81070a1043ed1f81923679f..d3b91e75cecd0df7895033aff87f588c6ba2d4df 100644 (file)
@@ -115,6 +115,7 @@ static void tk_xtime_add(struct timekeeper *tk, const struct timespec *ts)
 {
        tk->xtime_sec += ts->tv_sec;
        tk->xtime_nsec += (u64)ts->tv_nsec << tk->shift;
+       tk_normalize_xtime(tk);
 }
 
 static void tk_set_wall_to_mono(struct timekeeper *tk, struct timespec wtm)
@@ -276,7 +277,7 @@ static void timekeeping_forward_now(struct timekeeper *tk)
        tk->xtime_nsec += cycle_delta * tk->mult;
 
        /* If arch requires, add in gettimeoffset() */
-       tk->xtime_nsec += arch_gettimeoffset() << tk->shift;
+       tk->xtime_nsec += (u64)arch_gettimeoffset() << tk->shift;
 
        tk_normalize_xtime(tk);
 
@@ -302,10 +303,11 @@ void getnstimeofday(struct timespec *ts)
                seq = read_seqbegin(&tk->lock);
 
                ts->tv_sec = tk->xtime_sec;
-               ts->tv_nsec = timekeeping_get_ns(tk);
+               nsecs = timekeeping_get_ns(tk);
 
        } while (read_seqretry(&tk->lock, seq));
 
+       ts->tv_nsec = 0;
        timespec_add_ns(ts, nsecs);
 }
 EXPORT_SYMBOL(getnstimeofday);
@@ -344,6 +346,7 @@ void ktime_get_ts(struct timespec *ts)
 {
        struct timekeeper *tk = &timekeeper;
        struct timespec tomono;
+       s64 nsec;
        unsigned int seq;
 
        WARN_ON(timekeeping_suspended);
@@ -351,13 +354,14 @@ void ktime_get_ts(struct timespec *ts)
        do {
                seq = read_seqbegin(&tk->lock);
                ts->tv_sec = tk->xtime_sec;
-               ts->tv_nsec = timekeeping_get_ns(tk);
+               nsec = timekeeping_get_ns(tk);
                tomono = tk->wall_to_monotonic;
 
        } while (read_seqretry(&tk->lock, seq));
 
-       set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec,
-                               ts->tv_nsec + tomono.tv_nsec);
+       ts->tv_sec += tomono.tv_sec;
+       ts->tv_nsec = 0;
+       timespec_add_ns(ts, nsec + tomono.tv_nsec);
 }
 EXPORT_SYMBOL_GPL(ktime_get_ts);
 
@@ -427,7 +431,7 @@ int do_settimeofday(const struct timespec *tv)
        struct timespec ts_delta, xt;
        unsigned long flags;
 
-       if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC)
+       if (!timespec_valid_strict(tv))
                return -EINVAL;
 
        write_seqlock_irqsave(&tk->lock, flags);
@@ -463,6 +467,8 @@ int timekeeping_inject_offset(struct timespec *ts)
 {
        struct timekeeper *tk = &timekeeper;
        unsigned long flags;
+       struct timespec tmp;
+       int ret = 0;
 
        if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
                return -EINVAL;
@@ -471,10 +477,17 @@ int timekeeping_inject_offset(struct timespec *ts)
 
        timekeeping_forward_now(tk);
 
+       /* Make sure the proposed value is valid */
+       tmp = timespec_add(tk_xtime(tk),  *ts);
+       if (!timespec_valid_strict(&tmp)) {
+               ret = -EINVAL;
+               goto error;
+       }
 
        tk_xtime_add(tk, ts);
        tk_set_wall_to_mono(tk, timespec_sub(tk->wall_to_monotonic, *ts));
 
+error: /* even if we error out, we forwarded the time, so call update */
        timekeeping_update(tk, true);
 
        write_sequnlock_irqrestore(&tk->lock, flags);
@@ -482,7 +495,7 @@ int timekeeping_inject_offset(struct timespec *ts)
        /* signal hrtimers about time change */
        clock_was_set();
 
-       return 0;
+       return ret;
 }
 EXPORT_SYMBOL(timekeeping_inject_offset);
 
@@ -649,7 +662,20 @@ void __init timekeeping_init(void)
        struct timespec now, boot, tmp;
 
        read_persistent_clock(&now);
+       if (!timespec_valid_strict(&now)) {
+               pr_warn("WARNING: Persistent clock returned invalid value!\n"
+                       "         Check your CMOS/BIOS settings.\n");
+               now.tv_sec = 0;
+               now.tv_nsec = 0;
+       }
+
        read_boot_clock(&boot);
+       if (!timespec_valid_strict(&boot)) {
+               pr_warn("WARNING: Boot clock returned invalid value!\n"
+                       "         Check your CMOS/BIOS settings.\n");
+               boot.tv_sec = 0;
+               boot.tv_nsec = 0;
+       }
 
        seqlock_init(&tk->lock);
 
@@ -690,7 +716,7 @@ static struct timespec timekeeping_suspend_time;
 static void __timekeeping_inject_sleeptime(struct timekeeper *tk,
                                                        struct timespec *delta)
 {
-       if (!timespec_valid(delta)) {
+       if (!timespec_valid_strict(delta)) {
                printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid "
                                        "sleep delta value!\n");
                return;
@@ -1129,6 +1155,10 @@ static void update_wall_time(void)
        offset = (clock->read(clock) - clock->cycle_last) & clock->mask;
 #endif
 
+       /* Check if there's really nothing to do */
+       if (offset < tk->cycle_interval)
+               goto out;
+
        /*
         * With NO_HZ we may have to accumulate many cycle_intervals
         * (think "ticks") worth of time at once. To do this efficiently,
@@ -1161,9 +1191,9 @@ static void update_wall_time(void)
        * the vsyscall implementations are converted to use xtime_nsec
        * (shifted nanoseconds), this can be killed.
        */
-       remainder = tk->xtime_nsec & ((1 << tk->shift) - 1);
+       remainder = tk->xtime_nsec & ((1ULL << tk->shift) - 1);
        tk->xtime_nsec -= remainder;
-       tk->xtime_nsec += 1 << tk->shift;
+       tk->xtime_nsec += 1ULL << tk->shift;
        tk->ntp_error += remainder << tk->ntp_error_shift;
 
        /*
@@ -1217,6 +1247,7 @@ void get_monotonic_boottime(struct timespec *ts)
 {
        struct timekeeper *tk = &timekeeper;
        struct timespec tomono, sleep;
+       s64 nsec;
        unsigned int seq;
 
        WARN_ON(timekeeping_suspended);
@@ -1224,14 +1255,15 @@ void get_monotonic_boottime(struct timespec *ts)
        do {
                seq = read_seqbegin(&tk->lock);
                ts->tv_sec = tk->xtime_sec;
-               ts->tv_nsec = timekeeping_get_ns(tk);
+               nsec = timekeeping_get_ns(tk);
                tomono = tk->wall_to_monotonic;
                sleep = tk->total_sleep_time;
 
        } while (read_seqretry(&tk->lock, seq));
 
-       set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,
-                       ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec);
+       ts->tv_sec += tomono.tv_sec + sleep.tv_sec;
+       ts->tv_nsec = 0;
+       timespec_add_ns(ts, nsec + tomono.tv_nsec + sleep.tv_nsec);
 }
 EXPORT_SYMBOL_GPL(get_monotonic_boottime);