Commit c3c433e4 authored by Shaohua Li's avatar Shaohua Li Committed by Linus Torvalds

[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: default avatarShaohua Li <shaohua.li@intel.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 57c4ce3c
...@@ -383,6 +383,7 @@ void notify_arch_cmos_timer(void) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 = { ...@@ -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) ...@@ -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) ...@@ -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 = { ...@@ -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) ...@@ -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 = { ...@@ -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 = {
......
#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 { ...@@ -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 {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment