Merge tag 'rproc-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson...
[linux-2.6-microblaze.git] / kernel / time / ntp.c
index 069ca78..7404d38 100644 (file)
@@ -494,65 +494,74 @@ out:
        return leap;
 }
 
+#if defined(CONFIG_GENERIC_CMOS_UPDATE) || defined(CONFIG_RTC_SYSTOHC)
 static void sync_hw_clock(struct work_struct *work);
-static DECLARE_DELAYED_WORK(sync_work, sync_hw_clock);
-
-static void sched_sync_hw_clock(struct timespec64 now,
-                               unsigned long target_nsec, bool fail)
+static DECLARE_WORK(sync_work, sync_hw_clock);
+static struct hrtimer sync_hrtimer;
+#define SYNC_PERIOD_NS (11UL * 60 * NSEC_PER_SEC)
 
+static enum hrtimer_restart sync_timer_callback(struct hrtimer *timer)
 {
-       struct timespec64 next;
-
-       ktime_get_real_ts64(&next);
-       if (!fail)
-               next.tv_sec = 659;
-       else {
-               /*
-                * Try again as soon as possible. Delaying long periods
-                * decreases the accuracy of the work queue timer. Due to this
-                * the algorithm is very likely to require a short-sleep retry
-                * after the above long sleep to synchronize ts_nsec.
-                */
-               next.tv_sec = 0;
-       }
-
-       /* Compute the needed delay that will get to tv_nsec == target_nsec */
-       next.tv_nsec = target_nsec - next.tv_nsec;
-       if (next.tv_nsec <= 0)
-               next.tv_nsec += NSEC_PER_SEC;
-       if (next.tv_nsec >= NSEC_PER_SEC) {
-               next.tv_sec++;
-               next.tv_nsec -= NSEC_PER_SEC;
-       }
+       queue_work(system_power_efficient_wq, &sync_work);
 
-       queue_delayed_work(system_power_efficient_wq, &sync_work,
-                          timespec64_to_jiffies(&next));
+       return HRTIMER_NORESTART;
 }
 
-static void sync_rtc_clock(void)
+static void sched_sync_hw_clock(unsigned long offset_nsec, bool retry)
 {
-       unsigned long target_nsec;
-       struct timespec64 adjust, now;
-       int rc;
+       ktime_t exp = ktime_set(ktime_get_real_seconds(), 0);
 
-       if (!IS_ENABLED(CONFIG_RTC_SYSTOHC))
-               return;
+       if (retry)
+               exp = ktime_add_ns(exp, 2 * NSEC_PER_SEC - offset_nsec);
+       else
+               exp = ktime_add_ns(exp, SYNC_PERIOD_NS - offset_nsec);
 
-       ktime_get_real_ts64(&now);
+       hrtimer_start(&sync_hrtimer, exp, HRTIMER_MODE_ABS);
+}
 
-       adjust = now;
-       if (persistent_clock_is_local)
-               adjust.tv_sec -= (sys_tz.tz_minuteswest * 60);
+/*
+ * Check whether @now is correct versus the required time to update the RTC
+ * and calculate the value which needs to be written to the RTC so that the
+ * next seconds increment of the RTC after the write is aligned with the next
+ * seconds increment of clock REALTIME.
+ *
+ * tsched     t1 write(t2.tv_sec - 1sec))      t2 RTC increments seconds
+ *
+ * t2.tv_nsec == 0
+ * tsched = t2 - set_offset_nsec
+ * newval = t2 - NSEC_PER_SEC
+ *
+ * ==> neval = tsched + set_offset_nsec - NSEC_PER_SEC
+ *
+ * As the execution of this code is not guaranteed to happen exactly at
+ * tsched this allows it to happen within a fuzzy region:
+ *
+ *     abs(now - tsched) < FUZZ
+ *
+ * If @now is not inside the allowed window the function returns false.
+ */
+static inline bool rtc_tv_nsec_ok(unsigned long set_offset_nsec,
+                                 struct timespec64 *to_set,
+                                 const struct timespec64 *now)
+{
+       /* Allowed error in tv_nsec, arbitarily set to 5 jiffies in ns. */
+       const unsigned long TIME_SET_NSEC_FUZZ = TICK_NSEC * 5;
+       struct timespec64 delay = {.tv_sec = -1,
+                                  .tv_nsec = set_offset_nsec};
 
-       /*
-        * The current RTC in use will provide the target_nsec it wants to be
-        * called at, and does rtc_tv_nsec_ok internally.
-        */
-       rc = rtc_set_ntp_time(adjust, &target_nsec);
-       if (rc == -ENODEV)
-               return;
+       *to_set = timespec64_add(*now, delay);
+
+       if (to_set->tv_nsec < TIME_SET_NSEC_FUZZ) {
+               to_set->tv_nsec = 0;
+               return true;
+       }
 
-       sched_sync_hw_clock(now, target_nsec, rc);
+       if (to_set->tv_nsec > NSEC_PER_SEC - TIME_SET_NSEC_FUZZ) {
+               to_set->tv_sec++;
+               to_set->tv_nsec = 0;
+               return true;
+       }
+       return false;
 }
 
 #ifdef CONFIG_GENERIC_CMOS_UPDATE
@@ -560,48 +569,47 @@ int __weak update_persistent_clock64(struct timespec64 now64)
 {
        return -ENODEV;
 }
+#else
+static inline int update_persistent_clock64(struct timespec64 now64)
+{
+       return -ENODEV;
+}
 #endif
 
-static bool sync_cmos_clock(void)
+#ifdef CONFIG_RTC_SYSTOHC
+/* Save NTP synchronized time to the RTC */
+static int update_rtc(struct timespec64 *to_set, unsigned long *offset_nsec)
 {
-       static bool no_cmos;
-       struct timespec64 now;
-       struct timespec64 adjust;
-       int rc = -EPROTO;
-       long target_nsec = NSEC_PER_SEC / 2;
+       struct rtc_device *rtc;
+       struct rtc_time tm;
+       int err = -ENODEV;
 
-       if (!IS_ENABLED(CONFIG_GENERIC_CMOS_UPDATE))
-               return false;
+       rtc = rtc_class_open(CONFIG_RTC_SYSTOHC_DEVICE);
+       if (!rtc)
+               return -ENODEV;
 
-       if (no_cmos)
-               return false;
+       if (!rtc->ops || !rtc->ops->set_time)
+               goto out_close;
 
-       /*
-        * Historically update_persistent_clock64() has followed x86
-        * semantics, which match the MC146818A/etc RTC. This RTC will store
-        * 'adjust' and then in .5s it will advance once second.
-        *
-        * Architectures are strongly encouraged to use rtclib and not
-        * implement this legacy API.
-        */
-       ktime_get_real_ts64(&now);
-       if (rtc_tv_nsec_ok(-1 * target_nsec, &adjust, &now)) {
-               if (persistent_clock_is_local)
-                       adjust.tv_sec -= (sys_tz.tz_minuteswest * 60);
-               rc = update_persistent_clock64(adjust);
-               /*
-                * The machine does not support update_persistent_clock64 even
-                * though it defines CONFIG_GENERIC_CMOS_UPDATE.
-                */
-               if (rc == -ENODEV) {
-                       no_cmos = true;
-                       return false;
-               }
+       /* First call might not have the correct offset */
+       if (*offset_nsec == rtc->set_offset_nsec) {
+               rtc_time64_to_tm(to_set->tv_sec, &tm);
+               err = rtc_set_time(rtc, &tm);
+       } else {
+               /* Store the update offset and let the caller try again */
+               *offset_nsec = rtc->set_offset_nsec;
+               err = -EAGAIN;
        }
-
-       sched_sync_hw_clock(now, target_nsec, rc);
-       return true;
+out_close:
+       rtc_class_close(rtc);
+       return err;
+}
+#else
+static inline int update_rtc(struct timespec64 *to_set, unsigned long *offset_nsec)
+{
+       return -ENODEV;
 }
+#endif
 
 /*
  * If we have an externally synchronized Linux clock, then update RTC clock
@@ -613,24 +621,64 @@ static bool sync_cmos_clock(void)
  */
 static void sync_hw_clock(struct work_struct *work)
 {
-       if (!ntp_synced())
-               return;
+       /*
+        * The default synchronization offset is 500ms for the deprecated
+        * update_persistent_clock64() under the assumption that it uses
+        * the infamous CMOS clock (MC146818).
+        */
+       static unsigned long offset_nsec = NSEC_PER_SEC / 2;
+       struct timespec64 now, to_set;
+       int res = -EAGAIN;
 
-       if (sync_cmos_clock())
+       /*
+        * Don't update if STA_UNSYNC is set and if ntp_notify_cmos_timer()
+        * managed to schedule the work between the timer firing and the
+        * work being able to rearm the timer. Wait for the timer to expire.
+        */
+       if (!ntp_synced() || hrtimer_is_queued(&sync_hrtimer))
                return;
 
-       sync_rtc_clock();
+       ktime_get_real_ts64(&now);
+       /* If @now is not in the allowed window, try again */
+       if (!rtc_tv_nsec_ok(offset_nsec, &to_set, &now))
+               goto rearm;
+
+       /* Take timezone adjusted RTCs into account */
+       if (persistent_clock_is_local)
+               to_set.tv_sec -= (sys_tz.tz_minuteswest * 60);
+
+       /* Try the legacy RTC first. */
+       res = update_persistent_clock64(to_set);
+       if (res != -ENODEV)
+               goto rearm;
+
+       /* Try the RTC class */
+       res = update_rtc(&to_set, &offset_nsec);
+       if (res == -ENODEV)
+               return;
+rearm:
+       sched_sync_hw_clock(offset_nsec, res != 0);
 }
 
 void ntp_notify_cmos_timer(void)
 {
-       if (!ntp_synced())
-               return;
+       /*
+        * When the work is currently executed but has not yet the timer
+        * rearmed this queues the work immediately again. No big issue,
+        * just a pointless work scheduled.
+        */
+       if (ntp_synced() && !hrtimer_is_queued(&sync_hrtimer))
+               queue_work(system_power_efficient_wq, &sync_work);
+}
 
-       if (IS_ENABLED(CONFIG_GENERIC_CMOS_UPDATE) ||
-           IS_ENABLED(CONFIG_RTC_SYSTOHC))
-               queue_delayed_work(system_power_efficient_wq, &sync_work, 0);
+static void __init ntp_init_cmos_sync(void)
+{
+       hrtimer_init(&sync_hrtimer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
+       sync_hrtimer.function = sync_timer_callback;
 }
+#else /* CONFIG_GENERIC_CMOS_UPDATE) || defined(CONFIG_RTC_SYSTOHC) */
+static inline void __init ntp_init_cmos_sync(void) { }
+#endif /* !CONFIG_GENERIC_CMOS_UPDATE) || defined(CONFIG_RTC_SYSTOHC) */
 
 /*
  * Propagate a new txc->status value into the NTP state:
@@ -1044,4 +1092,5 @@ __setup("ntp_tick_adj=", ntp_tick_adj_setup);
 void __init ntp_init(void)
 {
        ntp_clear();
+       ntp_init_cmos_sync();
 }