[PATCH] rtc: adapt allowed RTC update error

From: Miroslav Lichvar
Date: Tue Dec 01 2020 - 09:40:27 EST


When the system clock is marked as synchronized via adjtimex(), the
kernel is expected to copy the system time to the RTC every 11 minutes.

There are reports that it doesn't always work reliably. It seems the
current requirement for the RTC update to happen within 5 ticks of the
target time in some cases can consistently fail for hours or even days.

It is better to set the RTC with a larger error than let it drift for
too long.

Add a static variable to rtc_tv_nsec_ok() to count the checks. With each
failed check, relax the requirement by one jiffie, and reset the counter
when it finally succeeds. This should allow the RTC update to happen in
a minute at most.

Signed-off-by: Miroslav Lichvar <mlichvar@xxxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: John Stultz <john.stultz@xxxxxxxxxx>
Cc: Prarit Bhargava <prarit@xxxxxxxxxx>
Cc: Jason Gunthorpe <jgg@xxxxxxxx>
---
include/linux/rtc.h | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/include/linux/rtc.h b/include/linux/rtc.h
index 22d1575e4991..8d105f10ef6a 100644
--- a/include/linux/rtc.h
+++ b/include/linux/rtc.h
@@ -218,21 +218,30 @@ static inline bool rtc_tv_nsec_ok(s64 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 = 0,
.tv_nsec = set_offset_nsec};
+ unsigned long time_set_nsec_fuzz;
+ static unsigned int attempt;

*to_set = timespec64_add(*now, delay);

- if (to_set->tv_nsec < TIME_SET_NSEC_FUZZ) {
+ /*
+ * Determine allowed error in tv_nsec. Start at 5 jiffies and add a
+ * jiffie with each failed attempt to make sure the RTC will be set at
+ * some point, even if the update cannot be scheduled very accurately.
+ */
+ time_set_nsec_fuzz = (5 + attempt++) * TICK_NSEC;
+
+ if (to_set->tv_nsec < time_set_nsec_fuzz) {
to_set->tv_nsec = 0;
+ attempt = 0;
return true;
}

- if (to_set->tv_nsec > NSEC_PER_SEC - TIME_SET_NSEC_FUZZ) {
+ if (to_set->tv_nsec > NSEC_PER_SEC - time_set_nsec_fuzz) {
to_set->tv_sec++;
to_set->tv_nsec = 0;
+ attempt = 0;
return true;
}
return false;
--
2.26.2