diff options
author | Shaohua Li <shaohua.li@intel.com> | 2005-09-03 18:57:07 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@evo.osdl.org> | 2005-09-05 03:06:18 -0400 |
commit | c3c433e4f33afe255389ba3b1a003dc8deb3de9a (patch) | |
tree | 071304e15e21e0a93c17050000a682f4ae1a98c1 | |
parent | 57c4ce3cbfba1bb0da7f37b9328a713cbd5d0919 (diff) |
[PATCH] add suspend/resume for timer
The timers lack .suspend/.resume methods. Because of this, jiffies got a
big compensation after a S3 resume. And then softlockup watchdog reports
an oops. This occured with HPET enabled, but it's also possible for other
timers.
Signed-off-by: Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | arch/i386/kernel/time.c | 10 | ||||
-rw-r--r-- | arch/i386/kernel/timers/timer_hpet.c | 14 | ||||
-rw-r--r-- | arch/i386/kernel/timers/timer_pit.c | 27 | ||||
-rw-r--r-- | arch/i386/kernel/timers/timer_pm.c | 9 | ||||
-rw-r--r-- | arch/i386/kernel/timers/timer_tsc.c | 14 | ||||
-rw-r--r-- | include/asm-i386/timer.h | 3 |
6 files changed, 50 insertions, 27 deletions
diff --git a/arch/i386/kernel/time.c b/arch/i386/kernel/time.c index 0ee9dee8af06..6f794a78ee1e 100644 --- a/arch/i386/kernel/time.c +++ b/arch/i386/kernel/time.c | |||
@@ -383,6 +383,7 @@ void notify_arch_cmos_timer(void) | |||
383 | 383 | ||
384 | static long clock_cmos_diff, sleep_start; | 384 | static long clock_cmos_diff, sleep_start; |
385 | 385 | ||
386 | static struct timer_opts *last_timer; | ||
386 | static int timer_suspend(struct sys_device *dev, pm_message_t state) | 387 | static int timer_suspend(struct sys_device *dev, pm_message_t state) |
387 | { | 388 | { |
388 | /* | 389 | /* |
@@ -391,6 +392,10 @@ static int timer_suspend(struct sys_device *dev, pm_message_t state) | |||
391 | clock_cmos_diff = -get_cmos_time(); | 392 | clock_cmos_diff = -get_cmos_time(); |
392 | clock_cmos_diff += get_seconds(); | 393 | clock_cmos_diff += get_seconds(); |
393 | sleep_start = get_cmos_time(); | 394 | sleep_start = get_cmos_time(); |
395 | last_timer = cur_timer; | ||
396 | cur_timer = &timer_none; | ||
397 | if (last_timer->suspend) | ||
398 | last_timer->suspend(state); | ||
394 | return 0; | 399 | return 0; |
395 | } | 400 | } |
396 | 401 | ||
@@ -404,6 +409,7 @@ static int timer_resume(struct sys_device *dev) | |||
404 | if (is_hpet_enabled()) | 409 | if (is_hpet_enabled()) |
405 | hpet_reenable(); | 410 | hpet_reenable(); |
406 | #endif | 411 | #endif |
412 | setup_pit_timer(); | ||
407 | sec = get_cmos_time() + clock_cmos_diff; | 413 | sec = get_cmos_time() + clock_cmos_diff; |
408 | sleep_length = (get_cmos_time() - sleep_start) * HZ; | 414 | sleep_length = (get_cmos_time() - sleep_start) * HZ; |
409 | write_seqlock_irqsave(&xtime_lock, flags); | 415 | write_seqlock_irqsave(&xtime_lock, flags); |
@@ -412,6 +418,10 @@ static int timer_resume(struct sys_device *dev) | |||
412 | write_sequnlock_irqrestore(&xtime_lock, flags); | 418 | write_sequnlock_irqrestore(&xtime_lock, flags); |
413 | jiffies += sleep_length; | 419 | jiffies += sleep_length; |
414 | wall_jiffies += sleep_length; | 420 | wall_jiffies += sleep_length; |
421 | if (last_timer->resume) | ||
422 | last_timer->resume(); | ||
423 | cur_timer = last_timer; | ||
424 | last_timer = NULL; | ||
415 | return 0; | 425 | return 0; |
416 | } | 426 | } |
417 | 427 | ||
diff --git a/arch/i386/kernel/timers/timer_hpet.c b/arch/i386/kernel/timers/timer_hpet.c index cbb3f221ced8..001de97c9e4a 100644 --- a/arch/i386/kernel/timers/timer_hpet.c +++ b/arch/i386/kernel/timers/timer_hpet.c | |||
@@ -181,6 +181,19 @@ static int __init init_hpet(char* override) | |||
181 | return 0; | 181 | return 0; |
182 | } | 182 | } |
183 | 183 | ||
184 | static int hpet_resume(void) | ||
185 | { | ||
186 | write_seqlock(&monotonic_lock); | ||
187 | /* Assume this is the last mark offset time */ | ||
188 | rdtsc(last_tsc_low, last_tsc_high); | ||
189 | |||
190 | if (hpet_use_timer) | ||
191 | hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick; | ||
192 | else | ||
193 | hpet_last = hpet_readl(HPET_COUNTER); | ||
194 | write_sequnlock(&monotonic_lock); | ||
195 | return 0; | ||
196 | } | ||
184 | /************************************************************/ | 197 | /************************************************************/ |
185 | 198 | ||
186 | /* tsc timer_opts struct */ | 199 | /* tsc timer_opts struct */ |
@@ -190,6 +203,7 @@ static struct timer_opts timer_hpet __read_mostly = { | |||
190 | .get_offset = get_offset_hpet, | 203 | .get_offset = get_offset_hpet, |
191 | .monotonic_clock = monotonic_clock_hpet, | 204 | .monotonic_clock = monotonic_clock_hpet, |
192 | .delay = delay_hpet, | 205 | .delay = delay_hpet, |
206 | .resume = hpet_resume, | ||
193 | }; | 207 | }; |
194 | 208 | ||
195 | struct init_timer_opts __initdata timer_hpet_init = { | 209 | struct init_timer_opts __initdata timer_hpet_init = { |
diff --git a/arch/i386/kernel/timers/timer_pit.c b/arch/i386/kernel/timers/timer_pit.c index 06de036a820c..eddb64038234 100644 --- a/arch/i386/kernel/timers/timer_pit.c +++ b/arch/i386/kernel/timers/timer_pit.c | |||
@@ -175,30 +175,3 @@ void setup_pit_timer(void) | |||
175 | outb(LATCH >> 8 , PIT_CH0); /* MSB */ | 175 | outb(LATCH >> 8 , PIT_CH0); /* MSB */ |
176 | spin_unlock_irqrestore(&i8253_lock, flags); | 176 | spin_unlock_irqrestore(&i8253_lock, flags); |
177 | } | 177 | } |
178 | |||
179 | static int timer_resume(struct sys_device *dev) | ||
180 | { | ||
181 | setup_pit_timer(); | ||
182 | return 0; | ||
183 | } | ||
184 | |||
185 | static struct sysdev_class timer_sysclass = { | ||
186 | set_kset_name("timer_pit"), | ||
187 | .resume = timer_resume, | ||
188 | }; | ||
189 | |||
190 | static struct sys_device device_timer = { | ||
191 | .id = 0, | ||
192 | .cls = &timer_sysclass, | ||
193 | }; | ||
194 | |||
195 | static int __init init_timer_sysfs(void) | ||
196 | { | ||
197 | int error = sysdev_class_register(&timer_sysclass); | ||
198 | if (!error) | ||
199 | error = sysdev_register(&device_timer); | ||
200 | return error; | ||
201 | } | ||
202 | |||
203 | device_initcall(init_timer_sysfs); | ||
204 | |||
diff --git a/arch/i386/kernel/timers/timer_pm.c b/arch/i386/kernel/timers/timer_pm.c index 4ef20e663498..264edaaac315 100644 --- a/arch/i386/kernel/timers/timer_pm.c +++ b/arch/i386/kernel/timers/timer_pm.c | |||
@@ -186,6 +186,14 @@ static void mark_offset_pmtmr(void) | |||
186 | } | 186 | } |
187 | } | 187 | } |
188 | 188 | ||
189 | static int pmtmr_resume(void) | ||
190 | { | ||
191 | write_seqlock(&monotonic_lock); | ||
192 | /* Assume this is the last mark offset time */ | ||
193 | offset_tick = read_pmtmr(); | ||
194 | write_sequnlock(&monotonic_lock); | ||
195 | return 0; | ||
196 | } | ||
189 | 197 | ||
190 | static unsigned long long monotonic_clock_pmtmr(void) | 198 | static unsigned long long monotonic_clock_pmtmr(void) |
191 | { | 199 | { |
@@ -247,6 +255,7 @@ static struct timer_opts timer_pmtmr = { | |||
247 | .monotonic_clock = monotonic_clock_pmtmr, | 255 | .monotonic_clock = monotonic_clock_pmtmr, |
248 | .delay = delay_pmtmr, | 256 | .delay = delay_pmtmr, |
249 | .read_timer = read_timer_tsc, | 257 | .read_timer = read_timer_tsc, |
258 | .resume = pmtmr_resume, | ||
250 | }; | 259 | }; |
251 | 260 | ||
252 | struct init_timer_opts __initdata timer_pmtmr_init = { | 261 | struct init_timer_opts __initdata timer_pmtmr_init = { |
diff --git a/arch/i386/kernel/timers/timer_tsc.c b/arch/i386/kernel/timers/timer_tsc.c index 8f4e4d5bc560..6dd470cc9f72 100644 --- a/arch/i386/kernel/timers/timer_tsc.c +++ b/arch/i386/kernel/timers/timer_tsc.c | |||
@@ -543,6 +543,19 @@ static int __init init_tsc(char* override) | |||
543 | return -ENODEV; | 543 | return -ENODEV; |
544 | } | 544 | } |
545 | 545 | ||
546 | static int tsc_resume(void) | ||
547 | { | ||
548 | write_seqlock(&monotonic_lock); | ||
549 | /* Assume this is the last mark offset time */ | ||
550 | rdtsc(last_tsc_low, last_tsc_high); | ||
551 | #ifdef CONFIG_HPET_TIMER | ||
552 | if (is_hpet_enabled() && hpet_use_timer) | ||
553 | hpet_last = hpet_readl(HPET_COUNTER); | ||
554 | #endif | ||
555 | write_sequnlock(&monotonic_lock); | ||
556 | return 0; | ||
557 | } | ||
558 | |||
546 | #ifndef CONFIG_X86_TSC | 559 | #ifndef CONFIG_X86_TSC |
547 | /* disable flag for tsc. Takes effect by clearing the TSC cpu flag | 560 | /* disable flag for tsc. Takes effect by clearing the TSC cpu flag |
548 | * in cpu/common.c */ | 561 | * in cpu/common.c */ |
@@ -573,6 +586,7 @@ static struct timer_opts timer_tsc = { | |||
573 | .monotonic_clock = monotonic_clock_tsc, | 586 | .monotonic_clock = monotonic_clock_tsc, |
574 | .delay = delay_tsc, | 587 | .delay = delay_tsc, |
575 | .read_timer = read_timer_tsc, | 588 | .read_timer = read_timer_tsc, |
589 | .resume = tsc_resume, | ||
576 | }; | 590 | }; |
577 | 591 | ||
578 | struct init_timer_opts __initdata timer_tsc_init = { | 592 | struct init_timer_opts __initdata timer_tsc_init = { |
diff --git a/include/asm-i386/timer.h b/include/asm-i386/timer.h index dcf1e07db08a..aed16437479d 100644 --- a/include/asm-i386/timer.h +++ b/include/asm-i386/timer.h | |||
@@ -1,6 +1,7 @@ | |||
1 | #ifndef _ASMi386_TIMER_H | 1 | #ifndef _ASMi386_TIMER_H |
2 | #define _ASMi386_TIMER_H | 2 | #define _ASMi386_TIMER_H |
3 | #include <linux/init.h> | 3 | #include <linux/init.h> |
4 | #include <linux/pm.h> | ||
4 | 5 | ||
5 | /** | 6 | /** |
6 | * struct timer_ops - used to define a timer source | 7 | * struct timer_ops - used to define a timer source |
@@ -23,6 +24,8 @@ struct timer_opts { | |||
23 | unsigned long long (*monotonic_clock)(void); | 24 | unsigned long long (*monotonic_clock)(void); |
24 | void (*delay)(unsigned long); | 25 | void (*delay)(unsigned long); |
25 | unsigned long (*read_timer)(void); | 26 | unsigned long (*read_timer)(void); |
27 | int (*suspend)(pm_message_t state); | ||
28 | int (*resume)(void); | ||
26 | }; | 29 | }; |
27 | 30 | ||
28 | struct init_timer_opts { | 31 | struct init_timer_opts { |