[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
|
@ -383,6 +383,7 @@ void notify_arch_cmos_timer(void)
|
||||||
|
|
||||||
static long clock_cmos_diff, sleep_start;
|
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)
|
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_cmos_time();
|
||||||
clock_cmos_diff += get_seconds();
|
clock_cmos_diff += get_seconds();
|
||||||
sleep_start = get_cmos_time();
|
sleep_start = get_cmos_time();
|
||||||
|
last_timer = cur_timer;
|
||||||
|
cur_timer = &timer_none;
|
||||||
|
if (last_timer->suspend)
|
||||||
|
last_timer->suspend(state);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,6 +409,7 @@ static int timer_resume(struct sys_device *dev)
|
||||||
if (is_hpet_enabled())
|
if (is_hpet_enabled())
|
||||||
hpet_reenable();
|
hpet_reenable();
|
||||||
#endif
|
#endif
|
||||||
|
setup_pit_timer();
|
||||||
sec = get_cmos_time() + clock_cmos_diff;
|
sec = get_cmos_time() + clock_cmos_diff;
|
||||||
sleep_length = (get_cmos_time() - sleep_start) * HZ;
|
sleep_length = (get_cmos_time() - sleep_start) * HZ;
|
||||||
write_seqlock_irqsave(&xtime_lock, flags);
|
write_seqlock_irqsave(&xtime_lock, flags);
|
||||||
|
@ -412,6 +418,10 @@ static int timer_resume(struct sys_device *dev)
|
||||||
write_sequnlock_irqrestore(&xtime_lock, flags);
|
write_sequnlock_irqrestore(&xtime_lock, flags);
|
||||||
jiffies += sleep_length;
|
jiffies += sleep_length;
|
||||||
wall_jiffies += sleep_length;
|
wall_jiffies += sleep_length;
|
||||||
|
if (last_timer->resume)
|
||||||
|
last_timer->resume();
|
||||||
|
cur_timer = last_timer;
|
||||||
|
last_timer = NULL;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,19 @@ static int __init init_hpet(char* override)
|
||||||
return 0;
|
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 */
|
/* tsc timer_opts struct */
|
||||||
|
@ -190,6 +203,7 @@ static struct timer_opts timer_hpet __read_mostly = {
|
||||||
.get_offset = get_offset_hpet,
|
.get_offset = get_offset_hpet,
|
||||||
.monotonic_clock = monotonic_clock_hpet,
|
.monotonic_clock = monotonic_clock_hpet,
|
||||||
.delay = delay_hpet,
|
.delay = delay_hpet,
|
||||||
|
.resume = hpet_resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct init_timer_opts __initdata timer_hpet_init = {
|
struct init_timer_opts __initdata timer_hpet_init = {
|
||||||
|
|
|
@ -175,30 +175,3 @@ void setup_pit_timer(void)
|
||||||
outb(LATCH >> 8 , PIT_CH0); /* MSB */
|
outb(LATCH >> 8 , PIT_CH0); /* MSB */
|
||||||
spin_unlock_irqrestore(&i8253_lock, flags);
|
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)
|
static unsigned long long monotonic_clock_pmtmr(void)
|
||||||
{
|
{
|
||||||
|
@ -247,6 +255,7 @@ static struct timer_opts timer_pmtmr = {
|
||||||
.monotonic_clock = monotonic_clock_pmtmr,
|
.monotonic_clock = monotonic_clock_pmtmr,
|
||||||
.delay = delay_pmtmr,
|
.delay = delay_pmtmr,
|
||||||
.read_timer = read_timer_tsc,
|
.read_timer = read_timer_tsc,
|
||||||
|
.resume = pmtmr_resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct init_timer_opts __initdata timer_pmtmr_init = {
|
struct init_timer_opts __initdata timer_pmtmr_init = {
|
||||||
|
|
|
@ -543,6 +543,19 @@ static int __init init_tsc(char* override)
|
||||||
return -ENODEV;
|
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
|
#ifndef CONFIG_X86_TSC
|
||||||
/* disable flag for tsc. Takes effect by clearing the TSC cpu flag
|
/* disable flag for tsc. Takes effect by clearing the TSC cpu flag
|
||||||
* in cpu/common.c */
|
* in cpu/common.c */
|
||||||
|
@ -573,6 +586,7 @@ static struct timer_opts timer_tsc = {
|
||||||
.monotonic_clock = monotonic_clock_tsc,
|
.monotonic_clock = monotonic_clock_tsc,
|
||||||
.delay = delay_tsc,
|
.delay = delay_tsc,
|
||||||
.read_timer = read_timer_tsc,
|
.read_timer = read_timer_tsc,
|
||||||
|
.resume = tsc_resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct init_timer_opts __initdata timer_tsc_init = {
|
struct init_timer_opts __initdata timer_tsc_init = {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef _ASMi386_TIMER_H
|
#ifndef _ASMi386_TIMER_H
|
||||||
#define _ASMi386_TIMER_H
|
#define _ASMi386_TIMER_H
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
#include <linux/pm.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct timer_ops - used to define a timer source
|
* struct timer_ops - used to define a timer source
|
||||||
|
@ -23,6 +24,8 @@ struct timer_opts {
|
||||||
unsigned long long (*monotonic_clock)(void);
|
unsigned long long (*monotonic_clock)(void);
|
||||||
void (*delay)(unsigned long);
|
void (*delay)(unsigned long);
|
||||||
unsigned long (*read_timer)(void);
|
unsigned long (*read_timer)(void);
|
||||||
|
int (*suspend)(pm_message_t state);
|
||||||
|
int (*resume)(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct init_timer_opts {
|
struct init_timer_opts {
|
||||||
|
|
Loading…
Reference in a new issue