]> 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 cbc6acb0db3fafbaa4830da34cdb22e538892764..937098aab49877b670409f49eb869fec9e20cfd1 100644 (file)
@@ -29,6 +29,9 @@ static struct timekeeper timekeeper;
 /* flag for if timekeeping is suspended */
 int __read_mostly timekeeping_suspended;
 
+/* Flag for if there is a persistent clock on this platform */
+bool __read_mostly persistent_clock_exist = false;
+
 static inline void tk_normalize_xtime(struct timekeeper *tk)
 {
        while (tk->xtime_nsec >= ((u64)NSEC_PER_SEC << tk->shift)) {
@@ -135,6 +138,20 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
 }
 
 /* Timekeeper helper functions. */
+
+#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
+u32 (*arch_gettimeoffset)(void);
+
+u32 get_arch_timeoffset(void)
+{
+       if (likely(arch_gettimeoffset))
+               return arch_gettimeoffset();
+       return 0;
+}
+#else
+static inline u32 get_arch_timeoffset(void) { return 0; }
+#endif
+
 static inline s64 timekeeping_get_ns(struct timekeeper *tk)
 {
        cycle_t cycle_now, cycle_delta;
@@ -151,8 +168,8 @@ static inline s64 timekeeping_get_ns(struct timekeeper *tk)
        nsec = cycle_delta * tk->mult + tk->xtime_nsec;
        nsec >>= tk->shift;
 
-       /* If arch requires, add in gettimeoffset() */
-       return nsec + arch_gettimeoffset();
+       /* If arch requires, add in get_arch_timeoffset() */
+       return nsec + get_arch_timeoffset();
 }
 
 static inline s64 timekeeping_get_ns_raw(struct timekeeper *tk)
@@ -171,8 +188,8 @@ static inline s64 timekeeping_get_ns_raw(struct timekeeper *tk)
        /* convert delta to nanoseconds. */
        nsec = clocksource_cyc2ns(cycle_delta, clock->mult, clock->shift);
 
-       /* If arch requires, add in gettimeoffset() */
-       return nsec + arch_gettimeoffset();
+       /* If arch requires, add in get_arch_timeoffset() */
+       return nsec + get_arch_timeoffset();
 }
 
 static RAW_NOTIFIER_HEAD(pvclock_gtod_chain);
@@ -254,8 +271,8 @@ static void timekeeping_forward_now(struct timekeeper *tk)
 
        tk->xtime_nsec += cycle_delta * tk->mult;
 
-       /* If arch requires, add in gettimeoffset() */
-       tk->xtime_nsec += (u64)arch_gettimeoffset() << tk->shift;
+       /* If arch requires, add in get_arch_timeoffset() */
+       tk->xtime_nsec += (u64)get_arch_timeoffset() << tk->shift;
 
        tk_normalize_xtime(tk);
 
@@ -264,19 +281,18 @@ static void timekeeping_forward_now(struct timekeeper *tk)
 }
 
 /**
- * getnstimeofday - Returns the time of day in a timespec
+ * __getnstimeofday - Returns the time of day in a timespec.
  * @ts:                pointer to the timespec to be set
  *
- * Returns the time of day in a timespec.
+ * Updates the time of day in the timespec.
+ * Returns 0 on success, or -ve when suspended (timespec will be undefined).
  */
-void getnstimeofday(struct timespec *ts)
+int __getnstimeofday(struct timespec *ts)
 {
        struct timekeeper *tk = &timekeeper;
        unsigned long seq;
        s64 nsecs = 0;
 
-       WARN_ON(timekeeping_suspended);
-
        do {
                seq = read_seqbegin(&tk->lock);
 
@@ -287,6 +303,26 @@ void getnstimeofday(struct timespec *ts)
 
        ts->tv_nsec = 0;
        timespec_add_ns(ts, nsecs);
+
+       /*
+        * Do not bail out early, in case there were callers still using
+        * the value, even in the face of the WARN_ON.
+        */
+       if (unlikely(timekeeping_suspended))
+               return -EAGAIN;
+       return 0;
+}
+EXPORT_SYMBOL(__getnstimeofday);
+
+/**
+ * getnstimeofday - Returns the time of day in a timespec.
+ * @ts:                pointer to the timespec to be set
+ *
+ * Returns the time of day in a timespec (WARN if suspended).
+ */
+void getnstimeofday(struct timespec *ts)
+{
+       WARN_ON(__getnstimeofday(ts));
 }
 EXPORT_SYMBOL(getnstimeofday);
 
@@ -477,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
  *
@@ -640,12 +718,14 @@ 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;
-       }
+       } else if (now.tv_sec || now.tv_nsec)
+               persistent_clock_exist = true;
 
        read_boot_clock(&boot);
        if (!timespec_valid_strict(&boot)) {
@@ -718,11 +798,12 @@ void timekeeping_inject_sleeptime(struct timespec *delta)
 {
        struct timekeeper *tk = &timekeeper;
        unsigned long flags;
-       struct timespec ts;
 
-       /* Make sure we don't set the clock twice */
-       read_persistent_clock(&ts);
-       if (!(ts.tv_sec == 0 && ts.tv_nsec == 0))
+       /*
+        * Make sure we don't set the clock twice, as timekeeping_resume()
+        * already did it
+        */
+       if (has_persistent_clock())
                return;
 
        write_seqlock_irqsave(&tk->lock, flags);
@@ -749,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);
@@ -1060,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();
                }
        }