Commit c3c433e4f33afe255389ba3b1a003dc8deb3de9a

Authored by Shaohua Li
Committed by Linus Torvalds
1 parent 57c4ce3cbf

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