diff options
| author | Suresh Siddha <suresh.b.siddha@intel.com> | 2010-08-19 20:03:38 -0400 |
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2010-08-20 08:59:02 -0400 |
| commit | cd7240c0b900eb6d690ccee088a6c9b46dae815a (patch) | |
| tree | 0a1ed10298a2bb2c9d6010c4d03a7f9508bdcba6 | |
| parent | 861d034ee814917a83bd5de4b26e3b8336ddeeb8 (diff) | |
x86, tsc, sched: Recompute cyc2ns_offset's during resume from sleep states
TSC's get reset after suspend/resume (even on cpu's with invariant TSC
which runs at a constant rate across ACPI P-, C- and T-states). And in
some systems BIOS seem to reinit TSC to arbitrary large value (still
sync'd across cpu's) during resume.
This leads to a scenario of scheduler rq->clock (sched_clock_cpu()) less
than rq->age_stamp (introduced in 2.6.32). This leads to a big value
returned by scale_rt_power() and the resulting big group power set by the
update_group_power() is causing improper load balancing between busy and
idle cpu's after suspend/resume.
This resulted in multi-threaded workloads (like kernel-compilation) go
slower after suspend/resume cycle on core i5 laptops.
Fix this by recomputing cyc2ns_offset's during resume, so that
sched_clock() continues from the point where it was left off during
suspend.
Reported-by: Florian Pritz <flo@xssn.at>
Signed-off-by: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: <stable@kernel.org> # [v2.6.32+]
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
LKML-Reference: <1282262618.2675.24.camel@sbsiddha-MOBL3.sc.intel.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
| -rw-r--r-- | arch/x86/include/asm/tsc.h | 2 | ||||
| -rw-r--r-- | arch/x86/kernel/tsc.c | 38 | ||||
| -rw-r--r-- | arch/x86/power/cpu.c | 2 |
3 files changed, 42 insertions, 0 deletions
diff --git a/arch/x86/include/asm/tsc.h b/arch/x86/include/asm/tsc.h index c0427295e8f5..1ca132fc0d03 100644 --- a/arch/x86/include/asm/tsc.h +++ b/arch/x86/include/asm/tsc.h | |||
| @@ -59,5 +59,7 @@ extern void check_tsc_sync_source(int cpu); | |||
| 59 | extern void check_tsc_sync_target(void); | 59 | extern void check_tsc_sync_target(void); |
| 60 | 60 | ||
| 61 | extern int notsc_setup(char *); | 61 | extern int notsc_setup(char *); |
| 62 | extern void save_sched_clock_state(void); | ||
| 63 | extern void restore_sched_clock_state(void); | ||
| 62 | 64 | ||
| 63 | #endif /* _ASM_X86_TSC_H */ | 65 | #endif /* _ASM_X86_TSC_H */ |
diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c index ce8e50239332..d632934cb638 100644 --- a/arch/x86/kernel/tsc.c +++ b/arch/x86/kernel/tsc.c | |||
| @@ -626,6 +626,44 @@ static void set_cyc2ns_scale(unsigned long cpu_khz, int cpu) | |||
| 626 | local_irq_restore(flags); | 626 | local_irq_restore(flags); |
| 627 | } | 627 | } |
| 628 | 628 | ||
| 629 | static unsigned long long cyc2ns_suspend; | ||
| 630 | |||
| 631 | void save_sched_clock_state(void) | ||
| 632 | { | ||
| 633 | if (!sched_clock_stable) | ||
| 634 | return; | ||
| 635 | |||
| 636 | cyc2ns_suspend = sched_clock(); | ||
| 637 | } | ||
| 638 | |||
| 639 | /* | ||
| 640 | * Even on processors with invariant TSC, TSC gets reset in some the | ||
| 641 | * ACPI system sleep states. And in some systems BIOS seem to reinit TSC to | ||
| 642 | * arbitrary value (still sync'd across cpu's) during resume from such sleep | ||
| 643 | * states. To cope up with this, recompute the cyc2ns_offset for each cpu so | ||
| 644 | * that sched_clock() continues from the point where it was left off during | ||
| 645 | * suspend. | ||
| 646 | */ | ||
| 647 | void restore_sched_clock_state(void) | ||
| 648 | { | ||
| 649 | unsigned long long offset; | ||
| 650 | unsigned long flags; | ||
| 651 | int cpu; | ||
| 652 | |||
| 653 | if (!sched_clock_stable) | ||
| 654 | return; | ||
| 655 | |||
| 656 | local_irq_save(flags); | ||
| 657 | |||
| 658 | get_cpu_var(cyc2ns_offset) = 0; | ||
| 659 | offset = cyc2ns_suspend - sched_clock(); | ||
| 660 | |||
| 661 | for_each_possible_cpu(cpu) | ||
| 662 | per_cpu(cyc2ns_offset, cpu) = offset; | ||
| 663 | |||
| 664 | local_irq_restore(flags); | ||
| 665 | } | ||
| 666 | |||
| 629 | #ifdef CONFIG_CPU_FREQ | 667 | #ifdef CONFIG_CPU_FREQ |
| 630 | 668 | ||
| 631 | /* Frequency scaling support. Adjust the TSC based timer when the cpu frequency | 669 | /* Frequency scaling support. Adjust the TSC based timer when the cpu frequency |
diff --git a/arch/x86/power/cpu.c b/arch/x86/power/cpu.c index e7e8c5f54956..87bb35e34ef1 100644 --- a/arch/x86/power/cpu.c +++ b/arch/x86/power/cpu.c | |||
| @@ -113,6 +113,7 @@ static void __save_processor_state(struct saved_context *ctxt) | |||
| 113 | void save_processor_state(void) | 113 | void save_processor_state(void) |
| 114 | { | 114 | { |
| 115 | __save_processor_state(&saved_context); | 115 | __save_processor_state(&saved_context); |
| 116 | save_sched_clock_state(); | ||
| 116 | } | 117 | } |
| 117 | #ifdef CONFIG_X86_32 | 118 | #ifdef CONFIG_X86_32 |
| 118 | EXPORT_SYMBOL(save_processor_state); | 119 | EXPORT_SYMBOL(save_processor_state); |
| @@ -229,6 +230,7 @@ static void __restore_processor_state(struct saved_context *ctxt) | |||
| 229 | void restore_processor_state(void) | 230 | void restore_processor_state(void) |
| 230 | { | 231 | { |
| 231 | __restore_processor_state(&saved_context); | 232 | __restore_processor_state(&saved_context); |
| 233 | restore_sched_clock_state(); | ||
| 232 | } | 234 | } |
| 233 | #ifdef CONFIG_X86_32 | 235 | #ifdef CONFIG_X86_32 |
| 234 | EXPORT_SYMBOL(restore_processor_state); | 236 | EXPORT_SYMBOL(restore_processor_state); |
