diff options
Diffstat (limited to 'kernel/time/timekeeping.c')
-rw-r--r-- | kernel/time/timekeeping.c | 28 |
1 files changed, 28 insertions, 0 deletions
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 342408cf68dd..2b021b0e8507 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c | |||
@@ -604,6 +604,12 @@ static struct timespec timekeeping_suspend_time; | |||
604 | */ | 604 | */ |
605 | static void __timekeeping_inject_sleeptime(struct timespec *delta) | 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 | xtime = timespec_add(xtime, *delta); | 613 | xtime = timespec_add(xtime, *delta); |
608 | wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta); | 614 | wall_to_monotonic = timespec_sub(wall_to_monotonic, *delta); |
609 | total_sleep_time = timespec_add(total_sleep_time, *delta); | 615 | total_sleep_time = timespec_add(total_sleep_time, *delta); |
@@ -686,12 +692,34 @@ static void timekeeping_resume(void) | |||
686 | static int timekeeping_suspend(void) | 692 | static int timekeeping_suspend(void) |
687 | { | 693 | { |
688 | unsigned long flags; | 694 | unsigned long flags; |
695 | struct timespec delta, delta_delta; | ||
696 | static struct timespec old_delta; | ||
689 | 697 | ||
690 | read_persistent_clock(&timekeeping_suspend_time); | 698 | read_persistent_clock(&timekeeping_suspend_time); |
691 | 699 | ||
692 | write_seqlock_irqsave(&xtime_lock, flags); | 700 | write_seqlock_irqsave(&xtime_lock, flags); |
693 | timekeeping_forward_now(); | 701 | timekeeping_forward_now(); |
694 | timekeeping_suspended = 1; | 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 | write_sequnlock_irqrestore(&xtime_lock, flags); | 723 | write_sequnlock_irqrestore(&xtime_lock, flags); |
696 | 724 | ||
697 | clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL); | 725 | clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL); |