aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChen Yu <yu.c.chen@intel.com>2015-11-24 12:03:41 -0500
committerIngo Molnar <mingo@kernel.org>2015-11-26 04:04:53 -0500
commit7a9c2dd08eadd5c6943115dbbec040c38d2e0822 (patch)
tree95077b0503779f4213e434f0ed762ae5337ea748
parent6ffeba9607343f15303a399bc402a538800d89d9 (diff)
x86/pm: Introduce quirk framework to save/restore extra MSR registers around suspend/resume
A bug was reported that on certain Broadwell platforms, after resuming from S3, the CPU is running at an anomalously low speed. It turns out that the BIOS has modified the value of the THERM_CONTROL register during S3, and changed it from 0 to 0x10, thus enabled clock modulation(bit4), but with undefined CPU Duty Cycle(bit1:3) - which causes the problem. Here is a simple scenario to reproduce the issue: 1. Boot up the system 2. Get MSR 0x19a, it should be 0 3. Put the system into sleep, then wake it up 4. Get MSR 0x19a, it shows 0x10, while it should be 0 Although some BIOSen want to change the CPU Duty Cycle during S3, in our case we don't want the BIOS to do any modification. Fix this issue by introducing a more generic x86 framework to save/restore specified MSR registers(THERM_CONTROL in this case) for suspend/resume. This allows us to fix similar bugs in a much simpler way in the future. When the kernel wants to protect certain MSRs during suspending, we simply add a quirk entry in msr_save_dmi_table, and customize the MSR registers inside the quirk callback, for example: u32 msr_id_need_to_save[] = {MSR_ID0, MSR_ID1, MSR_ID2...}; and the quirk mechanism ensures that, once resumed from suspend, the MSRs indicated by these IDs will be restored to their original, pre-suspend values. Since both 64-bit and 32-bit kernels are affected, this patch covers the common 64/32-bit suspend/resume code path. And because the MSRs specified by the user might not be available or readable in any situation, we use rdmsrl_safe() to safely save these MSRs. Reported-and-tested-by: Marcin Kaszewski <marcin.kaszewski@intel.com> Signed-off-by: Chen Yu <yu.c.chen@intel.com> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Pavel Machek <pavel@ucw.cz> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: bp@suse.de Cc: len.brown@intel.com Cc: linux@horizon.com Cc: luto@kernel.org Cc: rjw@rjwysocki.net Link: http://lkml.kernel.org/r/c9abdcbc173dd2f57e8990e304376f19287e92ba.1448382971.git.yu.c.chen@intel.com [ More edits to the naming of data structures. ] Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r--arch/x86/include/asm/msr.h10
-rw-r--r--arch/x86/include/asm/suspend_32.h1
-rw-r--r--arch/x86/include/asm/suspend_64.h1
-rw-r--r--arch/x86/power/cpu.c92
4 files changed, 104 insertions, 0 deletions
diff --git a/arch/x86/include/asm/msr.h b/arch/x86/include/asm/msr.h
index 77d8b284e4a7..24feb3c1dc41 100644
--- a/arch/x86/include/asm/msr.h
+++ b/arch/x86/include/asm/msr.h
@@ -32,6 +32,16 @@ struct msr_regs_info {
32 int err; 32 int err;
33}; 33};
34 34
35struct saved_msr {
36 bool valid;
37 struct msr_info info;
38};
39
40struct saved_msrs {
41 unsigned int num;
42 struct saved_msr *array;
43};
44
35static inline unsigned long long native_read_tscp(unsigned int *aux) 45static inline unsigned long long native_read_tscp(unsigned int *aux)
36{ 46{
37 unsigned long low, high; 47 unsigned long low, high;
diff --git a/arch/x86/include/asm/suspend_32.h b/arch/x86/include/asm/suspend_32.h
index d1793f06854d..8e9dbe7b73a1 100644
--- a/arch/x86/include/asm/suspend_32.h
+++ b/arch/x86/include/asm/suspend_32.h
@@ -15,6 +15,7 @@ struct saved_context {
15 unsigned long cr0, cr2, cr3, cr4; 15 unsigned long cr0, cr2, cr3, cr4;
16 u64 misc_enable; 16 u64 misc_enable;
17 bool misc_enable_saved; 17 bool misc_enable_saved;
18 struct saved_msrs saved_msrs;
18 struct desc_ptr gdt_desc; 19 struct desc_ptr gdt_desc;
19 struct desc_ptr idt; 20 struct desc_ptr idt;
20 u16 ldt; 21 u16 ldt;
diff --git a/arch/x86/include/asm/suspend_64.h b/arch/x86/include/asm/suspend_64.h
index 7ebf0ebe4e68..6136a18152af 100644
--- a/arch/x86/include/asm/suspend_64.h
+++ b/arch/x86/include/asm/suspend_64.h
@@ -24,6 +24,7 @@ struct saved_context {
24 unsigned long cr0, cr2, cr3, cr4, cr8; 24 unsigned long cr0, cr2, cr3, cr4, cr8;
25 u64 misc_enable; 25 u64 misc_enable;
26 bool misc_enable_saved; 26 bool misc_enable_saved;
27 struct saved_msrs saved_msrs;
27 unsigned long efer; 28 unsigned long efer;
28 u16 gdt_pad; /* Unused */ 29 u16 gdt_pad; /* Unused */
29 struct desc_ptr gdt_desc; 30 struct desc_ptr gdt_desc;
diff --git a/arch/x86/power/cpu.c b/arch/x86/power/cpu.c
index 9ab52791fed5..d5f64996394a 100644
--- a/arch/x86/power/cpu.c
+++ b/arch/x86/power/cpu.c
@@ -23,6 +23,7 @@
23#include <asm/debugreg.h> 23#include <asm/debugreg.h>
24#include <asm/cpu.h> 24#include <asm/cpu.h>
25#include <asm/mmu_context.h> 25#include <asm/mmu_context.h>
26#include <linux/dmi.h>
26 27
27#ifdef CONFIG_X86_32 28#ifdef CONFIG_X86_32
28__visible unsigned long saved_context_ebx; 29__visible unsigned long saved_context_ebx;
@@ -32,6 +33,29 @@ __visible unsigned long saved_context_eflags;
32#endif 33#endif
33struct saved_context saved_context; 34struct saved_context saved_context;
34 35
36static void msr_save_context(struct saved_context *ctxt)
37{
38 struct saved_msr *msr = ctxt->saved_msrs.array;
39 struct saved_msr *end = msr + ctxt->saved_msrs.num;
40
41 while (msr < end) {
42 msr->valid = !rdmsrl_safe(msr->info.msr_no, &msr->info.reg.q);
43 msr++;
44 }
45}
46
47static void msr_restore_context(struct saved_context *ctxt)
48{
49 struct saved_msr *msr = ctxt->saved_msrs.array;
50 struct saved_msr *end = msr + ctxt->saved_msrs.num;
51
52 while (msr < end) {
53 if (msr->valid)
54 wrmsrl(msr->info.msr_no, msr->info.reg.q);
55 msr++;
56 }
57}
58
35/** 59/**
36 * __save_processor_state - save CPU registers before creating a 60 * __save_processor_state - save CPU registers before creating a
37 * hibernation image and before restoring the memory state from it 61 * hibernation image and before restoring the memory state from it
@@ -111,6 +135,7 @@ static void __save_processor_state(struct saved_context *ctxt)
111#endif 135#endif
112 ctxt->misc_enable_saved = !rdmsrl_safe(MSR_IA32_MISC_ENABLE, 136 ctxt->misc_enable_saved = !rdmsrl_safe(MSR_IA32_MISC_ENABLE,
113 &ctxt->misc_enable); 137 &ctxt->misc_enable);
138 msr_save_context(ctxt);
114} 139}
115 140
116/* Needed by apm.c */ 141/* Needed by apm.c */
@@ -229,6 +254,7 @@ static void notrace __restore_processor_state(struct saved_context *ctxt)
229 x86_platform.restore_sched_clock_state(); 254 x86_platform.restore_sched_clock_state();
230 mtrr_bp_restore(); 255 mtrr_bp_restore();
231 perf_restore_debug_store(); 256 perf_restore_debug_store();
257 msr_restore_context(ctxt);
232} 258}
233 259
234/* Needed by apm.c */ 260/* Needed by apm.c */
@@ -320,3 +346,69 @@ static int __init bsp_pm_check_init(void)
320} 346}
321 347
322core_initcall(bsp_pm_check_init); 348core_initcall(bsp_pm_check_init);
349
350static int msr_init_context(const u32 *msr_id, const int total_num)
351{
352 int i = 0;
353 struct saved_msr *msr_array;
354
355 if (saved_context.saved_msrs.array || saved_context.saved_msrs.num > 0) {
356 pr_err("x86/pm: MSR quirk already applied, please check your DMI match table.\n");
357 return -EINVAL;
358 }
359
360 msr_array = kmalloc_array(total_num, sizeof(struct saved_msr), GFP_KERNEL);
361 if (!msr_array) {
362 pr_err("x86/pm: Can not allocate memory to save/restore MSRs during suspend.\n");
363 return -ENOMEM;
364 }
365
366 for (i = 0; i < total_num; i++) {
367 msr_array[i].info.msr_no = msr_id[i];
368 msr_array[i].valid = false;
369 msr_array[i].info.reg.q = 0;
370 }
371 saved_context.saved_msrs.num = total_num;
372 saved_context.saved_msrs.array = msr_array;
373
374 return 0;
375}
376
377/*
378 * The following section is a quirk framework for problematic BIOSen:
379 * Sometimes MSRs are modified by the BIOSen after suspended to
380 * RAM, this might cause unexpected behavior after wakeup.
381 * Thus we save/restore these specified MSRs across suspend/resume
382 * in order to work around it.
383 *
384 * For any further problematic BIOSen/platforms,
385 * please add your own function similar to msr_initialize_bdw.
386 */
387static int msr_initialize_bdw(const struct dmi_system_id *d)
388{
389 /* Add any extra MSR ids into this array. */
390 u32 bdw_msr_id[] = { MSR_IA32_THERM_CONTROL };
391
392 pr_info("x86/pm: %s detected, MSR saving is needed during suspending.\n", d->ident);
393 return msr_init_context(bdw_msr_id, ARRAY_SIZE(bdw_msr_id));
394}
395
396static struct dmi_system_id msr_save_dmi_table[] = {
397 {
398 .callback = msr_initialize_bdw,
399 .ident = "BROADWELL BDX_EP",
400 .matches = {
401 DMI_MATCH(DMI_PRODUCT_NAME, "GRANTLEY"),
402 DMI_MATCH(DMI_PRODUCT_VERSION, "E63448-400"),
403 },
404 },
405 {}
406};
407
408static int pm_check_save_msr(void)
409{
410 dmi_check_system(msr_save_dmi_table);
411 return 0;
412}
413
414device_initcall(pm_check_save_msr);