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 /drivers/rtc | |
| 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
Diffstat (limited to 'drivers/rtc')
| -rw-r--r-- | drivers/rtc/class.c | 65 |
1 files changed, 48 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 | ||
