Commit 112ec469663e09ffc815761254b52f3ca787ce83

Authored by Linus Torvalds

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

... ... @@ -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);