timers/core changes for v3.1
Linus,
Please pull the latest timers-core-for-linus git tree from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip.git timers-core-for-linus
Thanks,
Ingo
------------------>
John Stultz (4):
time: Catch invalid timespec sleep values in __timekeeping_inject_sleeptime
time: Avoid accumulating time drift in suspend/resume
rtc: Avoid accumulating time drift in suspend/resume
time: Fix stupid KERN_WARN compile issue
drivers/rtc/class.c | 65 +++++++++++++++++++++++++++++++++------------ kernel/time/timekeeping.c | 28 +++++++++++++++++++ 2 files changed, 76 insertions(+), 17 deletions(-)
drivers/rtc/class.c +48 -17
@@ -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 ; }
kernel/time/timekeeping.c +28 -0
@@ -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 );