Commit c3c433e4f33afe255389ba3b1a003dc8deb3de9a
Committed by
Linus Torvalds
1 parent
57c4ce3cbf
Exists in
master
and in
4 other branches
[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>
Showing 6 changed files with 50 additions and 27 deletions Side-by-side Diff
arch/i386/kernel/time.c
| ... | ... | @@ -383,6 +383,7 @@ |
| 383 | 383 | |
| 384 | 384 | static long clock_cmos_diff, sleep_start; |
| 385 | 385 | |
| 386 | +static struct timer_opts *last_timer; | |
| 386 | 387 | static int timer_suspend(struct sys_device *dev, pm_message_t state) |
| 387 | 388 | { |
| 388 | 389 | /* |
| ... | ... | @@ -391,6 +392,10 @@ |
| 391 | 392 | clock_cmos_diff = -get_cmos_time(); |
| 392 | 393 | clock_cmos_diff += get_seconds(); |
| 393 | 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 | 399 | return 0; |
| 395 | 400 | } |
| 396 | 401 | |
| ... | ... | @@ -404,6 +409,7 @@ |
| 404 | 409 | if (is_hpet_enabled()) |
| 405 | 410 | hpet_reenable(); |
| 406 | 411 | #endif |
| 412 | + setup_pit_timer(); | |
| 407 | 413 | sec = get_cmos_time() + clock_cmos_diff; |
| 408 | 414 | sleep_length = (get_cmos_time() - sleep_start) * HZ; |
| 409 | 415 | write_seqlock_irqsave(&xtime_lock, flags); |
| ... | ... | @@ -412,6 +418,10 @@ |
| 412 | 418 | write_sequnlock_irqrestore(&xtime_lock, flags); |
| 413 | 419 | jiffies += sleep_length; |
| 414 | 420 | wall_jiffies += sleep_length; |
| 421 | + if (last_timer->resume) | |
| 422 | + last_timer->resume(); | |
| 423 | + cur_timer = last_timer; | |
| 424 | + last_timer = NULL; | |
| 415 | 425 | return 0; |
| 416 | 426 | } |
| 417 | 427 |
arch/i386/kernel/timers/timer_hpet.c
| ... | ... | @@ -181,6 +181,19 @@ |
| 181 | 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 | 199 | /* tsc timer_opts struct */ |
| ... | ... | @@ -190,6 +203,7 @@ |
| 190 | 203 | .get_offset = get_offset_hpet, |
| 191 | 204 | .monotonic_clock = monotonic_clock_hpet, |
| 192 | 205 | .delay = delay_hpet, |
| 206 | + .resume = hpet_resume, | |
| 193 | 207 | }; |
| 194 | 208 | |
| 195 | 209 | struct init_timer_opts __initdata timer_hpet_init = { |
arch/i386/kernel/timers/timer_pit.c
| ... | ... | @@ -175,30 +175,4 @@ |
| 175 | 175 | outb(LATCH >> 8 , PIT_CH0); /* MSB */ |
| 176 | 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); |
arch/i386/kernel/timers/timer_pm.c
| ... | ... | @@ -186,6 +186,14 @@ |
| 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 | 198 | static unsigned long long monotonic_clock_pmtmr(void) |
| 191 | 199 | { |
| ... | ... | @@ -247,6 +255,7 @@ |
| 247 | 255 | .monotonic_clock = monotonic_clock_pmtmr, |
| 248 | 256 | .delay = delay_pmtmr, |
| 249 | 257 | .read_timer = read_timer_tsc, |
| 258 | + .resume = pmtmr_resume, | |
| 250 | 259 | }; |
| 251 | 260 | |
| 252 | 261 | struct init_timer_opts __initdata timer_pmtmr_init = { |
arch/i386/kernel/timers/timer_tsc.c
| ... | ... | @@ -543,6 +543,19 @@ |
| 543 | 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 | 559 | #ifndef CONFIG_X86_TSC |
| 547 | 560 | /* disable flag for tsc. Takes effect by clearing the TSC cpu flag |
| 548 | 561 | * in cpu/common.c */ |
| ... | ... | @@ -573,6 +586,7 @@ |
| 573 | 586 | .monotonic_clock = monotonic_clock_tsc, |
| 574 | 587 | .delay = delay_tsc, |
| 575 | 588 | .read_timer = read_timer_tsc, |
| 589 | + .resume = tsc_resume, | |
| 576 | 590 | }; |
| 577 | 591 | |
| 578 | 592 | struct init_timer_opts __initdata timer_tsc_init = { |
include/asm-i386/timer.h
| 1 | 1 | #ifndef _ASMi386_TIMER_H |
| 2 | 2 | #define _ASMi386_TIMER_H |
| 3 | 3 | #include <linux/init.h> |
| 4 | +#include <linux/pm.h> | |
| 4 | 5 | |
| 5 | 6 | /** |
| 6 | 7 | * struct timer_ops - used to define a timer source |
| ... | ... | @@ -23,6 +24,8 @@ |
| 23 | 24 | unsigned long long (*monotonic_clock)(void); |
| 24 | 25 | void (*delay)(unsigned long); |
| 25 | 26 | unsigned long (*read_timer)(void); |
| 27 | + int (*suspend)(pm_message_t state); | |
| 28 | + int (*resume)(void); | |
| 26 | 29 | }; |
| 27 | 30 | |
| 28 | 31 | struct init_timer_opts { |