Merge branch 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 22 Jul 2011 23:52:18 +0000 (16:52 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 22 Jul 2011 23:52:18 +0000 (16:52 -0700)
* '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

drivers/rtc/class.c
kernel/time/timekeeping.c

index 4194e59..01a7df5 100644 (file)
@@ -41,20 +41,41 @@ static void rtc_device_release(struct device *dev)
  * system's wall clock; restore it on resume().
  */
 
-static time_t          oldtime;
-static struct timespec oldts;
+static struct timespec old_rtc, old_system, old_delta;
+
 
 static int rtc_suspend(struct device *dev, pm_message_t mesg)
 {
        struct rtc_device       *rtc = to_rtc_device(dev);
        struct rtc_time         tm;
-
+       struct timespec         delta, delta_delta;
        if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
                return 0;
 
+       /* snapshot the current RTC and system time at suspend*/
        rtc_read_time(rtc, &tm);
-       ktime_get_ts(&oldts);
-       rtc_tm_to_time(&tm, &oldtime);
+       getnstimeofday(&old_system);
+       rtc_tm_to_time(&tm, &old_rtc.tv_sec);
+
+
+       /*
+        * To avoid drift caused by repeated suspend/resumes,
+        * which each can add ~1 second drift error,
+        * try to compensate so the difference in system time
+        * and rtc time stays close to constant.
+        */
+       delta = timespec_sub(old_system, old_rtc);
+       delta_delta = timespec_sub(delta, old_delta);
+       if (abs(delta_delta.tv_sec)  >= 2) {
+               /*
+                * if delta_delta is too large, assume time correction
+                * has occured and set old_delta to the current delta.
+                */
+               old_delta = delta;
+       } else {
+               /* Otherwise try to adjust old_system to compensate */
+               old_system = timespec_sub(old_system, delta_delta);
+       }
 
        return 0;
 }
@@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev)
 {
        struct rtc_device       *rtc = to_rtc_device(dev);
        struct rtc_time         tm;
-       time_t                  newtime;
-       struct timespec         time;
-       struct timespec         newts;
+       struct timespec         new_system, new_rtc;
+       struct timespec         sleep_time;
 
        if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
                return 0;
 
-       ktime_get_ts(&newts);
+       /* snapshot the current rtc and system time at resume */
+       getnstimeofday(&new_system);
        rtc_read_time(rtc, &tm);
        if (rtc_valid_tm(&tm) != 0) {
                pr_debug("%s:  bogus resume time\n", dev_name(&rtc->dev));
                return 0;
        }
-       rtc_tm_to_time(&tm, &newtime);
-       if (newtime <= oldtime) {
-               if (newtime < oldtime)
+       rtc_tm_to_time(&tm, &new_rtc.tv_sec);
+       new_rtc.tv_nsec = 0;
+
+       if (new_rtc.tv_sec <= old_rtc.tv_sec) {
+               if (new_rtc.tv_sec < old_rtc.tv_sec)
                        pr_debug("%s:  time travel!\n", dev_name(&rtc->dev));
                return 0;
        }
-       /* calculate the RTC time delta */
-       set_normalized_timespec(&time, newtime - oldtime, 0);
 
-       /* subtract kernel time between rtc_suspend to rtc_resume */
-       time = timespec_sub(time, timespec_sub(newts, oldts));
+       /* calculate the RTC time delta (sleep time)*/
+       sleep_time = timespec_sub(new_rtc, old_rtc);
+
+       /*
+        * Since these RTC suspend/resume handlers are not called
+        * at the very end of suspend or the start of resume,
+        * some run-time may pass on either sides of the sleep time
+        * so subtract kernel run-time between rtc_suspend to rtc_resume
+        * to keep things accurate.
+        */
+       sleep_time = timespec_sub(sleep_time,
+                       timespec_sub(new_system, old_system));
 
-       timekeeping_inject_sleeptime(&time);
+       timekeeping_inject_sleeptime(&sleep_time);
        return 0;
 }
 
index 342408c..2b021b0 100644 (file)
@@ -604,6 +604,12 @@ static struct timespec timekeeping_suspend_time;
  */
 static void __timekeeping_inject_sleeptime(struct timespec *delta)
 {
+       if (!timespec_valid(delta)) {
+               printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid "
+                                       "sleep delta value!\n");
+               return;
+       }
+
        xtime = timespec_add(xtime, *delta);
        wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta);
        total_sleep_time = timespec_add(total_sleep_time, *delta);
@@ -686,12 +692,34 @@ static void timekeeping_resume(void)
 static int timekeeping_suspend(void)
 {
        unsigned long flags;
+       struct timespec         delta, delta_delta;
+       static struct timespec  old_delta;
 
        read_persistent_clock(&timekeeping_suspend_time);
 
        write_seqlock_irqsave(&xtime_lock, flags);
        timekeeping_forward_now();
        timekeeping_suspended = 1;
+
+       /*
+        * To avoid drift caused by repeated suspend/resumes,
+        * which each can add ~1 second drift error,
+        * try to compensate so the difference in system time
+        * and persistent_clock time stays close to constant.
+        */
+       delta = timespec_sub(xtime, timekeeping_suspend_time);
+       delta_delta = timespec_sub(delta, old_delta);
+       if (abs(delta_delta.tv_sec)  >= 2) {
+               /*
+                * if delta_delta is too large, assume time correction
+                * has occured and set old_delta to the current delta.
+                */
+               old_delta = delta;
+       } else {
+               /* Otherwise try to adjust old_system to compensate */
+               timekeeping_suspend_time =
+                       timespec_add(timekeeping_suspend_time, delta_delta);
+       }
        write_sequnlock_irqrestore(&xtime_lock, flags);
 
        clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);