Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

time: Fix a bug in timekeeping_suspend() with no persistent clock

When there's no persistent clock, normally
timekeeping_suspend_time should always be zero, but this can
break in timekeeping_suspend().

At T1, there was a system suspend, so old_delta was assigned T1.
After some time, one time adjustment happened, and xtime got the
value of T1-dt(0s<dt<2s). Then, there comes another system
suspend soon after this adjustment, obviously we will get a
small negative delta_delta, resulting in a negative
timekeeping_suspend_time.

This is problematic, when doing timekeeping_resume() if there is
no nonstop clocksource for example, it will hit the else leg and
inject the improper sleeptime which is the wrong logic.

So, we can solve this problem by only doing delta related code
when the persistent clock is existent. Actually the code only
makes sense for persistent clock cases.

Signed-off-by: Xunlei Pang <pang.xunlei@linaro.org>
Signed-off-by: John Stultz <john.stultz@linaro.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/1427945681-29972-18-git-send-email-john.stultz@linaro.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Xunlei Pang and committed by
Ingo Molnar
264bb3f7 814dcf8e

+19 -17
+19 -17
kernel/time/timekeeping.c
··· 1255 1255 raw_spin_unlock_irqrestore(&timekeeper_lock, flags); 1256 1256 } 1257 1257 1258 - /* time in seconds when suspend began */ 1258 + /* time in seconds when suspend began for persistent clock */ 1259 1259 static struct timespec64 timekeeping_suspend_time; 1260 1260 1261 1261 /** ··· 1428 1428 timekeeping_forward_now(tk); 1429 1429 timekeeping_suspended = 1; 1430 1430 1431 - /* 1432 - * To avoid drift caused by repeated suspend/resumes, 1433 - * which each can add ~1 second drift error, 1434 - * try to compensate so the difference in system time 1435 - * and persistent_clock time stays close to constant. 1436 - */ 1437 - delta = timespec64_sub(tk_xtime(tk), timekeeping_suspend_time); 1438 - delta_delta = timespec64_sub(delta, old_delta); 1439 - if (abs(delta_delta.tv_sec) >= 2) { 1431 + if (has_persistent_clock()) { 1440 1432 /* 1441 - * if delta_delta is too large, assume time correction 1442 - * has occured and set old_delta to the current delta. 1433 + * To avoid drift caused by repeated suspend/resumes, 1434 + * which each can add ~1 second drift error, 1435 + * try to compensate so the difference in system time 1436 + * and persistent_clock time stays close to constant. 1443 1437 */ 1444 - old_delta = delta; 1445 - } else { 1446 - /* Otherwise try to adjust old_system to compensate */ 1447 - timekeeping_suspend_time = 1448 - timespec64_add(timekeeping_suspend_time, delta_delta); 1438 + delta = timespec64_sub(tk_xtime(tk), timekeeping_suspend_time); 1439 + delta_delta = timespec64_sub(delta, old_delta); 1440 + if (abs(delta_delta.tv_sec) >= 2) { 1441 + /* 1442 + * if delta_delta is too large, assume time correction 1443 + * has occurred and set old_delta to the current delta. 1444 + */ 1445 + old_delta = delta; 1446 + } else { 1447 + /* Otherwise try to adjust old_system to compensate */ 1448 + timekeeping_suspend_time = 1449 + timespec64_add(timekeeping_suspend_time, delta_delta); 1450 + } 1449 1451 } 1450 1452 1451 1453 timekeeping_update(tk, TK_MIRROR);