Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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>

authored by

Shaohua Li and committed by
Linus Torvalds
c3c433e4 57c4ce3c

+50 -27
+10
arch/i386/kernel/time.c
··· 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 /* ··· 392 391 clock_cmos_diff = -get_cmos_time(); 393 392 clock_cmos_diff += get_seconds(); 394 393 sleep_start = get_cmos_time(); 394 + last_timer = cur_timer; 395 + cur_timer = &timer_none; 396 + if (last_timer->suspend) 397 + last_timer->suspend(state); 395 398 return 0; 396 399 } 397 400 ··· 409 404 if (is_hpet_enabled()) 410 405 hpet_reenable(); 411 406 #endif 407 + setup_pit_timer(); 412 408 sec = get_cmos_time() + clock_cmos_diff; 413 409 sleep_length = (get_cmos_time() - sleep_start) * HZ; 414 410 write_seqlock_irqsave(&xtime_lock, flags); ··· 418 412 write_sequnlock_irqrestore(&xtime_lock, flags); 419 413 jiffies += sleep_length; 420 414 wall_jiffies += sleep_length; 415 + if (last_timer->resume) 416 + last_timer->resume(); 417 + cur_timer = last_timer; 418 + last_timer = NULL; 421 419 return 0; 422 420 } 423 421
+14
arch/i386/kernel/timers/timer_hpet.c
··· 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 */ ··· 203 190 .get_offset = get_offset_hpet, 204 191 .monotonic_clock = monotonic_clock_hpet, 205 192 .delay = delay_hpet, 193 + .resume = hpet_resume, 206 194 }; 207 195 208 196 struct init_timer_opts __initdata timer_hpet_init = {
-27
arch/i386/kernel/timers/timer_pit.c
··· 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); 204 -
+9
arch/i386/kernel/timers/timer_pm.c
··· 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 { ··· 255 247 .monotonic_clock = monotonic_clock_pmtmr, 256 248 .delay = delay_pmtmr, 257 249 .read_timer = read_timer_tsc, 250 + .resume = pmtmr_resume, 258 251 }; 259 252 260 253 struct init_timer_opts __initdata timer_pmtmr_init = {
+14
arch/i386/kernel/timers/timer_tsc.c
··· 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 */ ··· 586 573 .monotonic_clock = monotonic_clock_tsc, 587 574 .delay = delay_tsc, 588 575 .read_timer = read_timer_tsc, 576 + .resume = tsc_resume, 589 577 }; 590 578 591 579 struct init_timer_opts __initdata timer_tsc_init = {
+3
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 ··· 24 23 unsigned long long (*monotonic_clock)(void); 25 24 void (*delay)(unsigned long); 26 25 unsigned long (*read_timer)(void); 26 + int (*suspend)(pm_message_t state); 27 + int (*resume)(void); 27 28 }; 28 29 29 30 struct init_timer_opts {