[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>
This commit is contained in:
parent
57c4ce3cbf
commit
c3c433e4f3
6 changed files with 50 additions and 27 deletions
|
@ -383,6 +383,7 @@ void notify_arch_cmos_timer(void)
|
|||
|
||||
static long clock_cmos_diff, sleep_start;
|
||||
|
||||
static struct timer_opts *last_timer;
|
||||
static int timer_suspend(struct sys_device *dev, pm_message_t state)
|
||||
{
|
||||
/*
|
||||
|
@ -391,6 +392,10 @@ static int timer_suspend(struct sys_device *dev, pm_message_t state)
|
|||
clock_cmos_diff = -get_cmos_time();
|
||||
clock_cmos_diff += get_seconds();
|
||||
sleep_start = get_cmos_time();
|
||||
last_timer = cur_timer;
|
||||
cur_timer = &timer_none;
|
||||
if (last_timer->suspend)
|
||||
last_timer->suspend(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -404,6 +409,7 @@ static int timer_resume(struct sys_device *dev)
|
|||
if (is_hpet_enabled())
|
||||
hpet_reenable();
|
||||
#endif
|
||||
setup_pit_timer();
|
||||
sec = get_cmos_time() + clock_cmos_diff;
|
||||
sleep_length = (get_cmos_time() - sleep_start) * HZ;
|
||||
write_seqlock_irqsave(&xtime_lock, flags);
|
||||
|
@ -412,6 +418,10 @@ static int timer_resume(struct sys_device *dev)
|
|||
write_sequnlock_irqrestore(&xtime_lock, flags);
|
||||
jiffies += sleep_length;
|
||||
wall_jiffies += sleep_length;
|
||||
if (last_timer->resume)
|
||||
last_timer->resume();
|
||||
cur_timer = last_timer;
|
||||
last_timer = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -181,6 +181,19 @@ static int __init init_hpet(char* override)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int hpet_resume(void)
|
||||
{
|
||||
write_seqlock(&monotonic_lock);
|
||||
/* Assume this is the last mark offset time */
|
||||
rdtsc(last_tsc_low, last_tsc_high);
|
||||
|
||||
if (hpet_use_timer)
|
||||
hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick;
|
||||
else
|
||||
hpet_last = hpet_readl(HPET_COUNTER);
|
||||
write_sequnlock(&monotonic_lock);
|
||||
return 0;
|
||||
}
|
||||
/************************************************************/
|
||||
|
||||
/* tsc timer_opts struct */
|
||||
|
@ -190,6 +203,7 @@ static struct timer_opts timer_hpet __read_mostly = {
|
|||
.get_offset = get_offset_hpet,
|
||||
.monotonic_clock = monotonic_clock_hpet,
|
||||
.delay = delay_hpet,
|
||||
.resume = hpet_resume,
|
||||
};
|
||||
|
||||
struct init_timer_opts __initdata timer_hpet_init = {
|
||||
|
|
|
@ -175,30 +175,3 @@ void setup_pit_timer(void)
|
|||
outb(LATCH >> 8 , PIT_CH0); /* MSB */
|
||||
spin_unlock_irqrestore(&i8253_lock, flags);
|
||||
}
|
||||
|
||||
static int timer_resume(struct sys_device *dev)
|
||||
{
|
||||
setup_pit_timer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sysdev_class timer_sysclass = {
|
||||
set_kset_name("timer_pit"),
|
||||
.resume = timer_resume,
|
||||
};
|
||||
|
||||
static struct sys_device device_timer = {
|
||||
.id = 0,
|
||||
.cls = &timer_sysclass,
|
||||
};
|
||||
|
||||
static int __init init_timer_sysfs(void)
|
||||
{
|
||||
int error = sysdev_class_register(&timer_sysclass);
|
||||
if (!error)
|
||||
error = sysdev_register(&device_timer);
|
||||
return error;
|
||||
}
|
||||
|
||||
device_initcall(init_timer_sysfs);
|
||||
|
||||
|
|
|
@ -186,6 +186,14 @@ static void mark_offset_pmtmr(void)
|
|||
}
|
||||
}
|
||||
|
||||
static int pmtmr_resume(void)
|
||||
{
|
||||
write_seqlock(&monotonic_lock);
|
||||
/* Assume this is the last mark offset time */
|
||||
offset_tick = read_pmtmr();
|
||||
write_sequnlock(&monotonic_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long long monotonic_clock_pmtmr(void)
|
||||
{
|
||||
|
@ -247,6 +255,7 @@ static struct timer_opts timer_pmtmr = {
|
|||
.monotonic_clock = monotonic_clock_pmtmr,
|
||||
.delay = delay_pmtmr,
|
||||
.read_timer = read_timer_tsc,
|
||||
.resume = pmtmr_resume,
|
||||
};
|
||||
|
||||
struct init_timer_opts __initdata timer_pmtmr_init = {
|
||||
|
|
|
@ -543,6 +543,19 @@ static int __init init_tsc(char* override)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int tsc_resume(void)
|
||||
{
|
||||
write_seqlock(&monotonic_lock);
|
||||
/* Assume this is the last mark offset time */
|
||||
rdtsc(last_tsc_low, last_tsc_high);
|
||||
#ifdef CONFIG_HPET_TIMER
|
||||
if (is_hpet_enabled() && hpet_use_timer)
|
||||
hpet_last = hpet_readl(HPET_COUNTER);
|
||||
#endif
|
||||
write_sequnlock(&monotonic_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_X86_TSC
|
||||
/* disable flag for tsc. Takes effect by clearing the TSC cpu flag
|
||||
* in cpu/common.c */
|
||||
|
@ -573,6 +586,7 @@ static struct timer_opts timer_tsc = {
|
|||
.monotonic_clock = monotonic_clock_tsc,
|
||||
.delay = delay_tsc,
|
||||
.read_timer = read_timer_tsc,
|
||||
.resume = tsc_resume,
|
||||
};
|
||||
|
||||
struct init_timer_opts __initdata timer_tsc_init = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef _ASMi386_TIMER_H
|
||||
#define _ASMi386_TIMER_H
|
||||
#include <linux/init.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
/**
|
||||
* struct timer_ops - used to define a timer source
|
||||
|
@ -23,6 +24,8 @@ struct timer_opts {
|
|||
unsigned long long (*monotonic_clock)(void);
|
||||
void (*delay)(unsigned long);
|
||||
unsigned long (*read_timer)(void);
|
||||
int (*suspend)(pm_message_t state);
|
||||
int (*resume)(void);
|
||||
};
|
||||
|
||||
struct init_timer_opts {
|
||||
|
|
Loading…
Reference in a new issue