diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-22 19:52:18 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-22 19:52:18 -0400 |
commit | 112ec469663e09ffc815761254b52f3ca787ce83 (patch) | |
tree | 18a7d2300dc10b7c2c994107681dffc927589701 | |
parent | a99a7d1436f9375662f35ccac8f1a1e1b0302a11 (diff) | |
parent | cbaa51524b3224813814607177a00c350ee35d12 (diff) |
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
-rw-r--r-- | drivers/rtc/class.c | 65 | ||||
-rw-r--r-- | kernel/time/timekeeping.c | 28 |
2 files changed, 76 insertions, 17 deletions
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 4194e59e14cd..01a7df5317c1 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c | |||
@@ -41,20 +41,41 @@ static void rtc_device_release(struct device *dev) | |||
41 | * system's wall clock; restore it on resume(). | 41 | * system's wall clock; restore it on resume(). |
42 | */ | 42 | */ |
43 | 43 | ||
44 | static time_t oldtime; | 44 | static struct timespec old_rtc, old_system, old_delta; |
45 | static struct timespec oldts; | 45 | |
46 | 46 | ||
47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) | 47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) |
48 | { | 48 | { |
49 | struct rtc_device *rtc = to_rtc_device(dev); | 49 | struct rtc_device *rtc = to_rtc_device(dev); |
50 | struct rtc_time tm; | 50 | struct rtc_time tm; |
51 | 51 | struct timespec delta, delta_delta; | |
52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
53 | return 0; | 53 | return 0; |
54 | 54 | ||
55 | /* snapshot the current RTC and system time at suspend*/ | ||
55 | rtc_read_time(rtc, &tm); | 56 | rtc_read_time(rtc, &tm); |
56 | ktime_get_ts(&oldts); | 57 | getnstimeofday(&old_system); |
57 | rtc_tm_to_time(&tm, &oldtime); | 58 | rtc_tm_to_time(&tm, &old_rtc.tv_sec); |
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 | } | ||
58 | 79 | ||
59 | return 0; | 80 | return 0; |
60 | } | 81 | } |
@@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev) | |||
63 | { | 84 | { |
64 | struct rtc_device *rtc = to_rtc_device(dev); | 85 | struct rtc_device *rtc = to_rtc_device(dev); |
65 | struct rtc_time tm; | 86 | struct rtc_time tm; |
66 | time_t newtime; | 87 | struct timespec new_system, new_rtc; |
67 | struct timespec time; | 88 | struct timespec sleep_time; |
68 | struct timespec newts; | ||
69 | 89 | ||
70 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) | 90 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
71 | return 0; | 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 | rtc_read_time(rtc, &tm); | 95 | rtc_read_time(rtc, &tm); |
75 | if (rtc_valid_tm(&tm) != 0) { | 96 | if (rtc_valid_tm(&tm) != 0) { |
76 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); | 97 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); |
77 | return 0; | 98 | return 0; |
78 | } | 99 | } |
79 | rtc_tm_to_time(&tm, &newtime); | 100 | rtc_tm_to_time(&tm, &new_rtc.tv_sec); |
80 | if (newtime <= oldtime) { | 101 | new_rtc.tv_nsec = 0; |
81 | if (newtime < oldtime) | 102 | |
103 | if (new_rtc.tv_sec <= old_rtc.tv_sec) { | ||
104 | if (new_rtc.tv_sec < old_rtc.tv_sec) | ||
82 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); | 105 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); |
83 | return 0; | 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 */ | 109 | /* calculate the RTC time delta (sleep time)*/ |
89 | time = timespec_sub(time, timespec_sub(newts, oldts)); | 110 | sleep_time = timespec_sub(new_rtc, old_rtc); |
111 | |||
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)); | ||
90 | 121 | ||
91 | timekeeping_inject_sleeptime(&time); | 122 | timekeeping_inject_sleeptime(&sleep_time); |
92 | return 0; | 123 | return 0; |
93 | } | 124 | } |
94 | 125 | ||
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); |