aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/time
diff options
context:
space:
mode:
authorXunlei Pang <pang.xunlei@linaro.org>2015-04-01 23:34:37 -0400
committerIngo Molnar <mingo@kernel.org>2015-04-03 02:18:33 -0400
commit264bb3f79f2a465477cdcd2f0554e21aedc443a3 (patch)
tree7f048c66ba323cc1d1d6170e58e0bfd8313e9df5 /kernel/time
parent814dcf8ead04f5ebcec74af06c705b207887f0fa (diff)
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>
Diffstat (limited to 'kernel/time')
-rw-r--r--kernel/time/timekeeping.c36
1 files changed, 19 insertions, 17 deletions
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 3be559b6fd0a..b7db4916415b 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -1255,7 +1255,7 @@ void __init timekeeping_init(void)
1255 raw_spin_unlock_irqrestore(&timekeeper_lock, flags); 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 */
1259static struct timespec64 timekeeping_suspend_time; 1259static struct timespec64 timekeeping_suspend_time;
1260 1260
1261/** 1261/**
@@ -1428,24 +1428,26 @@ int timekeeping_suspend(void)
1428 timekeeping_forward_now(tk); 1428 timekeeping_forward_now(tk);
1429 timekeeping_suspended = 1; 1429 timekeeping_suspended = 1;
1430 1430
1431 /* 1431 if (has_persistent_clock()) {
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) {
1440 /* 1432 /*
1441 * if delta_delta is too large, assume time correction 1433 * To avoid drift caused by repeated suspend/resumes,
1442 * has occured and set old_delta to the current delta. 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; 1438 delta = timespec64_sub(tk_xtime(tk), timekeeping_suspend_time);
1445 } else { 1439 delta_delta = timespec64_sub(delta, old_delta);
1446 /* Otherwise try to adjust old_system to compensate */ 1440 if (abs(delta_delta.tv_sec) >= 2) {
1447 timekeeping_suspend_time = 1441 /*
1448 timespec64_add(timekeeping_suspend_time, delta_delta); 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 timekeeping_update(tk, TK_MIRROR); 1453 timekeeping_update(tk, TK_MIRROR);