]> Pileus Git - ~andy/linux/blobdiff - kernel/time/timekeeping.c
timekeeping: Move TAI managment into timekeeping core from ntp
[~andy/linux] / kernel / time / timekeeping.c
index 9a0bc98fbe1db40208b9025bbfcf51f34057f1dc..937098aab49877b670409f49eb869fec9e20cfd1 100644 (file)
@@ -513,6 +513,48 @@ error: /* even if we error out, we forwarded the time, so call update */
 }
 EXPORT_SYMBOL(timekeeping_inject_offset);
 
+
+/**
+ * timekeeping_get_tai_offset - Returns current TAI offset from UTC
+ *
+ */
+s32 timekeeping_get_tai_offset(void)
+{
+       struct timekeeper *tk = &timekeeper;
+       unsigned int seq;
+       s32 ret;
+
+       do {
+               seq = read_seqbegin(&tk->lock);
+               ret = tk->tai_offset;
+       } while (read_seqretry(&tk->lock, seq));
+
+       return ret;
+}
+
+/**
+ * __timekeeping_set_tai_offset - Lock free worker function
+ *
+ */
+void __timekeeping_set_tai_offset(struct timekeeper *tk, s32 tai_offset)
+{
+       tk->tai_offset = tai_offset;
+}
+
+/**
+ * timekeeping_set_tai_offset - Sets the current TAI offset from UTC
+ *
+ */
+void timekeeping_set_tai_offset(s32 tai_offset)
+{
+       struct timekeeper *tk = &timekeeper;
+       unsigned long flags;
+
+       write_seqlock_irqsave(&tk->lock, flags);
+       __timekeeping_set_tai_offset(tk, tai_offset);
+       write_sequnlock_irqrestore(&tk->lock, flags);
+}
+
 /**
  * change_clocksource - Swaps clocksources if a new one is available
  *
@@ -788,22 +830,66 @@ void timekeeping_inject_sleeptime(struct timespec *delta)
 static void timekeeping_resume(void)
 {
        struct timekeeper *tk = &timekeeper;
+       struct clocksource *clock = tk->clock;
        unsigned long flags;
-       struct timespec ts;
+       struct timespec ts_new, ts_delta;
+       cycle_t cycle_now, cycle_delta;
+       bool suspendtime_found = false;
 
-       read_persistent_clock(&ts);
+       read_persistent_clock(&ts_new);
 
        clockevents_resume();
        clocksource_resume();
 
        write_seqlock_irqsave(&tk->lock, flags);
 
-       if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {
-               ts = timespec_sub(ts, timekeeping_suspend_time);
-               __timekeeping_inject_sleeptime(tk, &ts);
+       /*
+        * After system resumes, we need to calculate the suspended time and
+        * compensate it for the OS time. There are 3 sources that could be
+        * used: Nonstop clocksource during suspend, persistent clock and rtc
+        * device.
+        *
+        * One specific platform may have 1 or 2 or all of them, and the
+        * preference will be:
+        *      suspend-nonstop clocksource -> persistent clock -> rtc
+        * The less preferred source will only be tried if there is no better
+        * usable source. The rtc part is handled separately in rtc core code.
+        */
+       cycle_now = clock->read(clock);
+       if ((clock->flags & CLOCK_SOURCE_SUSPEND_NONSTOP) &&
+               cycle_now > clock->cycle_last) {
+               u64 num, max = ULLONG_MAX;
+               u32 mult = clock->mult;
+               u32 shift = clock->shift;
+               s64 nsec = 0;
+
+               cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;
+
+               /*
+                * "cycle_delta * mutl" may cause 64 bits overflow, if the
+                * suspended time is too long. In that case we need do the
+                * 64 bits math carefully
+                */
+               do_div(max, mult);
+               if (cycle_delta > max) {
+                       num = div64_u64(cycle_delta, max);
+                       nsec = (((u64) max * mult) >> shift) * num;
+                       cycle_delta -= num * max;
+               }
+               nsec += ((u64) cycle_delta * mult) >> shift;
+
+               ts_delta = ns_to_timespec(nsec);
+               suspendtime_found = true;
+       } else if (timespec_compare(&ts_new, &timekeeping_suspend_time) > 0) {
+               ts_delta = timespec_sub(ts_new, timekeeping_suspend_time);
+               suspendtime_found = true;
        }
-       /* re-base the last cycle value */
-       tk->clock->cycle_last = tk->clock->read(tk->clock);
+
+       if (suspendtime_found)
+               __timekeeping_inject_sleeptime(tk, &ts_delta);
+
+       /* Re-base the last cycle value */
+       clock->cycle_last = cycle_now;
        tk->ntp_error = 0;
        timekeeping_suspended = 0;
        timekeeping_update(tk, false);
@@ -1099,6 +1185,8 @@ static inline void accumulate_nsecs_to_secs(struct timekeeper *tk)
                        tk_set_wall_to_mono(tk,
                                timespec_sub(tk->wall_to_monotonic, ts));
 
+                       __timekeeping_set_tai_offset(tk, tk->tai_offset - leap);
+
                        clock_was_set_delayed();
                }
        }