Merge branch 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/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
This commit is contained in:
commit
112ec46966
|
@ -41,20 +41,41 @@ static void rtc_device_release(struct device *dev)
|
||||||
* system's wall clock; restore it on resume().
|
* system's wall clock; restore it on resume().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static time_t oldtime;
|
static struct timespec old_rtc, old_system, old_delta;
|
||||||
static struct timespec oldts;
|
|
||||||
|
|
||||||
static int rtc_suspend(struct device *dev, pm_message_t mesg)
|
static int rtc_suspend(struct device *dev, pm_message_t mesg)
|
||||||
{
|
{
|
||||||
struct rtc_device *rtc = to_rtc_device(dev);
|
struct rtc_device *rtc = to_rtc_device(dev);
|
||||||
struct rtc_time tm;
|
struct rtc_time tm;
|
||||||
|
struct timespec delta, delta_delta;
|
||||||
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
|
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* snapshot the current RTC and system time at suspend*/
|
||||||
rtc_read_time(rtc, &tm);
|
rtc_read_time(rtc, &tm);
|
||||||
ktime_get_ts(&oldts);
|
getnstimeofday(&old_system);
|
||||||
rtc_tm_to_time(&tm, &oldtime);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct rtc_device *rtc = to_rtc_device(dev);
|
struct rtc_device *rtc = to_rtc_device(dev);
|
||||||
struct rtc_time tm;
|
struct rtc_time tm;
|
||||||
time_t newtime;
|
struct timespec new_system, new_rtc;
|
||||||
struct timespec time;
|
struct timespec sleep_time;
|
||||||
struct timespec newts;
|
|
||||||
|
|
||||||
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
|
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ktime_get_ts(&newts);
|
/* snapshot the current rtc and system time at resume */
|
||||||
|
getnstimeofday(&new_system);
|
||||||
rtc_read_time(rtc, &tm);
|
rtc_read_time(rtc, &tm);
|
||||||
if (rtc_valid_tm(&tm) != 0) {
|
if (rtc_valid_tm(&tm) != 0) {
|
||||||
pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev));
|
pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
rtc_tm_to_time(&tm, &newtime);
|
rtc_tm_to_time(&tm, &new_rtc.tv_sec);
|
||||||
if (newtime <= oldtime) {
|
new_rtc.tv_nsec = 0;
|
||||||
if (newtime < oldtime)
|
|
||||||
|
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));
|
pr_debug("%s: time travel!\n", dev_name(&rtc->dev));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* calculate the RTC time delta */
|
|
||||||
set_normalized_timespec(&time, newtime - oldtime, 0);
|
|
||||||
|
|
||||||
/* subtract kernel time between rtc_suspend to rtc_resume */
|
/* calculate the RTC time delta (sleep time)*/
|
||||||
time = timespec_sub(time, timespec_sub(newts, oldts));
|
sleep_time = timespec_sub(new_rtc, old_rtc);
|
||||||
|
|
||||||
timekeeping_inject_sleeptime(&time);
|
/*
|
||||||
|
* 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(&sleep_time);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -604,6 +604,12 @@ static struct timespec timekeeping_suspend_time;
|
||||||
*/
|
*/
|
||||||
static void __timekeeping_inject_sleeptime(struct timespec *delta)
|
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);
|
xtime = timespec_add(xtime, *delta);
|
||||||
wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta);
|
wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta);
|
||||||
total_sleep_time = timespec_add(total_sleep_time, *delta);
|
total_sleep_time = timespec_add(total_sleep_time, *delta);
|
||||||
|
@ -686,12 +692,34 @@ static void timekeeping_resume(void)
|
||||||
static int timekeeping_suspend(void)
|
static int timekeeping_suspend(void)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
struct timespec delta, delta_delta;
|
||||||
|
static struct timespec old_delta;
|
||||||
|
|
||||||
read_persistent_clock(&timekeeping_suspend_time);
|
read_persistent_clock(&timekeeping_suspend_time);
|
||||||
|
|
||||||
write_seqlock_irqsave(&xtime_lock, flags);
|
write_seqlock_irqsave(&xtime_lock, flags);
|
||||||
timekeeping_forward_now();
|
timekeeping_forward_now();
|
||||||
timekeeping_suspended = 1;
|
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);
|
write_sequnlock_irqrestore(&xtime_lock, flags);
|
||||||
|
|
||||||
clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);
|
clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);
|
||||||
|
|
Loading…
Reference in New Issue