Commit 112ec469663e09ffc815761254b52f3ca787ce83
Exists in
master
and in
4 other branches
Merge branch 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kerne…
…l/git/tip/linux-2.6-tip * 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip: time: Fix stupid KERN_WARN compile issue rtc: Avoid accumulating time drift in suspend/resume time: Avoid accumulating time drift in suspend/resume time: Catch invalid timespec sleep values in __timekeeping_inject_sleeptime
Showing 2 changed files Side-by-side Diff
drivers/rtc/class.c
... | ... | @@ -41,21 +41,42 @@ |
41 | 41 | * system's wall clock; restore it on resume(). |
42 | 42 | */ |
43 | 43 | |
44 | -static time_t oldtime; | |
45 | -static struct timespec oldts; | |
44 | +static struct timespec old_rtc, old_system, old_delta; | |
46 | 45 | |
46 | + | |
47 | 47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) |
48 | 48 | { |
49 | 49 | struct rtc_device *rtc = to_rtc_device(dev); |
50 | 50 | struct rtc_time tm; |
51 | - | |
51 | + struct timespec delta, delta_delta; | |
52 | 52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
53 | 53 | return 0; |
54 | 54 | |
55 | + /* snapshot the current RTC and system time at suspend*/ | |
55 | 56 | rtc_read_time(rtc, &tm); |
56 | - ktime_get_ts(&oldts); | |
57 | - rtc_tm_to_time(&tm, &oldtime); | |
57 | + getnstimeofday(&old_system); | |
58 | + rtc_tm_to_time(&tm, &old_rtc.tv_sec); | |
58 | 59 | |
60 | + | |
61 | + /* | |
62 | + * To avoid drift caused by repeated suspend/resumes, | |
63 | + * which each can add ~1 second drift error, | |
64 | + * try to compensate so the difference in system time | |
65 | + * and rtc time stays close to constant. | |
66 | + */ | |
67 | + delta = timespec_sub(old_system, old_rtc); | |
68 | + delta_delta = timespec_sub(delta, old_delta); | |
69 | + if (abs(delta_delta.tv_sec) >= 2) { | |
70 | + /* | |
71 | + * if delta_delta is too large, assume time correction | |
72 | + * has occured and set old_delta to the current delta. | |
73 | + */ | |
74 | + old_delta = delta; | |
75 | + } else { | |
76 | + /* Otherwise try to adjust old_system to compensate */ | |
77 | + old_system = timespec_sub(old_system, delta_delta); | |
78 | + } | |
79 | + | |
59 | 80 | return 0; |
60 | 81 | } |
61 | 82 | |
62 | 83 | |
63 | 84 | |
64 | 85 | |
65 | 86 | |
66 | 87 | |
... | ... | @@ -63,32 +84,42 @@ |
63 | 84 | { |
64 | 85 | struct rtc_device *rtc = to_rtc_device(dev); |
65 | 86 | struct rtc_time tm; |
66 | - time_t newtime; | |
67 | - struct timespec time; | |
68 | - struct timespec newts; | |
87 | + struct timespec new_system, new_rtc; | |
88 | + struct timespec sleep_time; | |
69 | 89 | |
70 | 90 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
71 | 91 | return 0; |
72 | 92 | |
73 | - ktime_get_ts(&newts); | |
93 | + /* snapshot the current rtc and system time at resume */ | |
94 | + getnstimeofday(&new_system); | |
74 | 95 | rtc_read_time(rtc, &tm); |
75 | 96 | if (rtc_valid_tm(&tm) != 0) { |
76 | 97 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); |
77 | 98 | return 0; |
78 | 99 | } |
79 | - rtc_tm_to_time(&tm, &newtime); | |
80 | - if (newtime <= oldtime) { | |
81 | - if (newtime < oldtime) | |
100 | + rtc_tm_to_time(&tm, &new_rtc.tv_sec); | |
101 | + new_rtc.tv_nsec = 0; | |
102 | + | |
103 | + if (new_rtc.tv_sec <= old_rtc.tv_sec) { | |
104 | + if (new_rtc.tv_sec < old_rtc.tv_sec) | |
82 | 105 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); |
83 | 106 | return 0; |
84 | 107 | } |
85 | - /* calculate the RTC time delta */ | |
86 | - set_normalized_timespec(&time, newtime - oldtime, 0); | |
87 | 108 | |
88 | - /* subtract kernel time between rtc_suspend to rtc_resume */ | |
89 | - time = timespec_sub(time, timespec_sub(newts, oldts)); | |
109 | + /* calculate the RTC time delta (sleep time)*/ | |
110 | + sleep_time = timespec_sub(new_rtc, old_rtc); | |
90 | 111 | |
91 | - timekeeping_inject_sleeptime(&time); | |
112 | + /* | |
113 | + * Since these RTC suspend/resume handlers are not called | |
114 | + * at the very end of suspend or the start of resume, | |
115 | + * some run-time may pass on either sides of the sleep time | |
116 | + * so subtract kernel run-time between rtc_suspend to rtc_resume | |
117 | + * to keep things accurate. | |
118 | + */ | |
119 | + sleep_time = timespec_sub(sleep_time, | |
120 | + timespec_sub(new_system, old_system)); | |
121 | + | |
122 | + timekeeping_inject_sleeptime(&sleep_time); | |
92 | 123 | return 0; |
93 | 124 | } |
94 | 125 |
kernel/time/timekeeping.c
... | ... | @@ -604,6 +604,12 @@ |
604 | 604 | */ |
605 | 605 | static void __timekeeping_inject_sleeptime(struct timespec *delta) |
606 | 606 | { |
607 | + if (!timespec_valid(delta)) { | |
608 | + printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid " | |
609 | + "sleep delta value!\n"); | |
610 | + return; | |
611 | + } | |
612 | + | |
607 | 613 | xtime = timespec_add(xtime, *delta); |
608 | 614 | wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta); |
609 | 615 | total_sleep_time = timespec_add(total_sleep_time, *delta); |
610 | 616 | |
... | ... | @@ -686,12 +692,34 @@ |
686 | 692 | static int timekeeping_suspend(void) |
687 | 693 | { |
688 | 694 | unsigned long flags; |
695 | + struct timespec delta, delta_delta; | |
696 | + static struct timespec old_delta; | |
689 | 697 | |
690 | 698 | read_persistent_clock(&timekeeping_suspend_time); |
691 | 699 | |
692 | 700 | write_seqlock_irqsave(&xtime_lock, flags); |
693 | 701 | timekeeping_forward_now(); |
694 | 702 | timekeeping_suspended = 1; |
703 | + | |
704 | + /* | |
705 | + * To avoid drift caused by repeated suspend/resumes, | |
706 | + * which each can add ~1 second drift error, | |
707 | + * try to compensate so the difference in system time | |
708 | + * and persistent_clock time stays close to constant. | |
709 | + */ | |
710 | + delta = timespec_sub(xtime, timekeeping_suspend_time); | |
711 | + delta_delta = timespec_sub(delta, old_delta); | |
712 | + if (abs(delta_delta.tv_sec) >= 2) { | |
713 | + /* | |
714 | + * if delta_delta is too large, assume time correction | |
715 | + * has occured and set old_delta to the current delta. | |
716 | + */ | |
717 | + old_delta = delta; | |
718 | + } else { | |
719 | + /* Otherwise try to adjust old_system to compensate */ | |
720 | + timekeeping_suspend_time = | |
721 | + timespec_add(timekeeping_suspend_time, delta_delta); | |
722 | + } | |
695 | 723 | write_sequnlock_irqrestore(&xtime_lock, flags); |
696 | 724 | |
697 | 725 | clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL); |