diff options
author | Andreas Herrmann <andreas.herrmann3@amd.com> | 2009-03-12 12:39:37 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-03-13 05:19:27 -0400 |
commit | 3ff42da5048649503e343a32be37b14a6a4e8aaf (patch) | |
tree | 9d34746890b6d7aa46c9f5b464eab5b2e8df497a /arch/x86/kernel/cpu/mtrr/generic.c | |
parent | 0d890355bff25e1dc03a577a90ed80741489ca54 (diff) |
x86: mtrr: don't modify RdDram/WrDram bits of fixed MTRRs
Impact: bug fix + BIOS workaround
BIOS is expected to clear the SYSCFG[MtrrFixDramModEn] on AMD CPUs
after fixed MTRRs are configured.
Some BIOSes do not clear SYSCFG[MtrrFixDramModEn] on BP (and on APs).
This can lead to obfuscation in Linux when this bit is not cleared on
BP but cleared on APs. A consequence of this is that the saved
fixed-MTRR state (from BP) differs from the fixed-MTRRs of APs --
because RdDram/WrDram bits are read as zero when
SYSCFG[MtrrFixDramModEn] is cleared -- and Linux tries to sync
fixed-MTRR state from BP to AP. This implies that Linux sets
SYSCFG[MtrrFixDramEn] and activates those bits.
More important is that (some) systems change these bits in SMM when
ACPI is enabled. Hence it is racy if Linux modifies RdMem/WrMem bits,
too.
(1) The patch modifies an old fix from Bernhard Kaindl to get
suspend/resume working on some Acer Laptops. Bernhard's patch
tried to sync RdMem/WrMem bits of fixed MTRR registers and that
helped on those old Laptops. (Don't ask me why -- can't test it
myself). But this old problem was not the motivation for the
patch. (See http://lkml.org/lkml/2007/4/3/110)
(2) The more important effect is to fix issues on some more current systems.
On those systems Linux panics or just freezes, see
http://bugzilla.kernel.org/show_bug.cgi?id=11541
(and also duplicates of this bug:
http://bugzilla.kernel.org/show_bug.cgi?id=11737
http://bugzilla.kernel.org/show_bug.cgi?id=11714)
The affected systems boot only using acpi=ht, acpi=off or
when the kernel is built with CONFIG_MTRR=n.
The acpi options prevent full enablement of ACPI. Obviously when
ACPI is enabled the BIOS/SMM modfies RdMem/WrMem bits. When
CONFIG_MTRR=y Linux also accesses and modifies those bits when it
needs to sync fixed-MTRRs across cores (Bernhard's fix, see (1)).
How do you synchronize that? You can't. As a consequence Linux
shouldn't touch those bits at all (Rationale are AMD's BKDGs which
recommend to clear the bit that makes RdMem/WrMem accessible).
This is the purpose of this patch. And (so far) this suffices to
fix (1) and (2).
I suggest not to touch RdDram/WrDram bits of fixed-MTRRs and
SYSCFG[MtrrFixDramEn] and to clear SYSCFG[MtrrFixDramModEn] as
suggested by AMD K8, and AMD family 10h/11h BKDGs.
BIOS is expected to do this anyway. This should avoid that
Linux and SMM tread on each other's toes ...
Signed-off-by: Andreas Herrmann <andreas.herrmann3@amd.com>
Cc: trenn@suse.de
Cc: Yinghai Lu <yinghai@kernel.org>
LKML-Reference: <20090312163937.GH20716@alberich.amd.com>
Cc: <stable@kernel.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/cpu/mtrr/generic.c')
-rw-r--r-- | arch/x86/kernel/cpu/mtrr/generic.c | 51 |
1 files changed, 30 insertions, 21 deletions
diff --git a/arch/x86/kernel/cpu/mtrr/generic.c b/arch/x86/kernel/cpu/mtrr/generic.c index 964403520100..950c434f793d 100644 --- a/arch/x86/kernel/cpu/mtrr/generic.c +++ b/arch/x86/kernel/cpu/mtrr/generic.c | |||
@@ -33,6 +33,32 @@ u64 mtrr_tom2; | |||
33 | struct mtrr_state_type mtrr_state = {}; | 33 | struct mtrr_state_type mtrr_state = {}; |
34 | EXPORT_SYMBOL_GPL(mtrr_state); | 34 | EXPORT_SYMBOL_GPL(mtrr_state); |
35 | 35 | ||
36 | /** | ||
37 | * BIOS is expected to clear MtrrFixDramModEn bit, see for example | ||
38 | * "BIOS and Kernel Developer's Guide for the AMD Athlon 64 and AMD | ||
39 | * Opteron Processors" (26094 Rev. 3.30 February 2006), section | ||
40 | * "13.2.1.2 SYSCFG Register": "The MtrrFixDramModEn bit should be set | ||
41 | * to 1 during BIOS initalization of the fixed MTRRs, then cleared to | ||
42 | * 0 for operation." | ||
43 | */ | ||
44 | static inline void k8_check_syscfg_dram_mod_en(void) | ||
45 | { | ||
46 | u32 lo, hi; | ||
47 | |||
48 | if (!((boot_cpu_data.x86_vendor == X86_VENDOR_AMD) && | ||
49 | (boot_cpu_data.x86 >= 0x0f))) | ||
50 | return; | ||
51 | |||
52 | rdmsr(MSR_K8_SYSCFG, lo, hi); | ||
53 | if (lo & K8_MTRRFIXRANGE_DRAM_MODIFY) { | ||
54 | printk(KERN_ERR FW_WARN "MTRR: CPU %u: SYSCFG[MtrrFixDramModEn]" | ||
55 | " not cleared by BIOS, clearing this bit\n", | ||
56 | smp_processor_id()); | ||
57 | lo &= ~K8_MTRRFIXRANGE_DRAM_MODIFY; | ||
58 | mtrr_wrmsr(MSR_K8_SYSCFG, lo, hi); | ||
59 | } | ||
60 | } | ||
61 | |||
36 | /* | 62 | /* |
37 | * Returns the effective MTRR type for the region | 63 | * Returns the effective MTRR type for the region |
38 | * Error returns: | 64 | * Error returns: |
@@ -166,6 +192,8 @@ get_fixed_ranges(mtrr_type * frs) | |||
166 | unsigned int *p = (unsigned int *) frs; | 192 | unsigned int *p = (unsigned int *) frs; |
167 | int i; | 193 | int i; |
168 | 194 | ||
195 | k8_check_syscfg_dram_mod_en(); | ||
196 | |||
169 | rdmsr(MTRRfix64K_00000_MSR, p[0], p[1]); | 197 | rdmsr(MTRRfix64K_00000_MSR, p[0], p[1]); |
170 | 198 | ||
171 | for (i = 0; i < 2; i++) | 199 | for (i = 0; i < 2; i++) |
@@ -306,27 +334,10 @@ void mtrr_wrmsr(unsigned msr, unsigned a, unsigned b) | |||
306 | } | 334 | } |
307 | 335 | ||
308 | /** | 336 | /** |
309 | * Enable and allow read/write of extended fixed-range MTRR bits on K8 CPUs | ||
310 | * see AMD publication no. 24593, chapter 3.2.1 for more information | ||
311 | */ | ||
312 | static inline void k8_enable_fixed_iorrs(void) | ||
313 | { | ||
314 | unsigned lo, hi; | ||
315 | |||
316 | rdmsr(MSR_K8_SYSCFG, lo, hi); | ||
317 | mtrr_wrmsr(MSR_K8_SYSCFG, lo | ||
318 | | K8_MTRRFIXRANGE_DRAM_ENABLE | ||
319 | | K8_MTRRFIXRANGE_DRAM_MODIFY, hi); | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * set_fixed_range - checks & updates a fixed-range MTRR if it differs from the value it should have | 337 | * set_fixed_range - checks & updates a fixed-range MTRR if it differs from the value it should have |
324 | * @msr: MSR address of the MTTR which should be checked and updated | 338 | * @msr: MSR address of the MTTR which should be checked and updated |
325 | * @changed: pointer which indicates whether the MTRR needed to be changed | 339 | * @changed: pointer which indicates whether the MTRR needed to be changed |
326 | * @msrwords: pointer to the MSR values which the MSR should have | 340 | * @msrwords: pointer to the MSR values which the MSR should have |
327 | * | ||
328 | * If K8 extentions are wanted, update the K8 SYSCFG MSR also. | ||
329 | * See AMD publication no. 24593, chapter 7.8.1, page 233 for more information. | ||
330 | */ | 341 | */ |
331 | static void set_fixed_range(int msr, bool *changed, unsigned int *msrwords) | 342 | static void set_fixed_range(int msr, bool *changed, unsigned int *msrwords) |
332 | { | 343 | { |
@@ -335,10 +346,6 @@ static void set_fixed_range(int msr, bool *changed, unsigned int *msrwords) | |||
335 | rdmsr(msr, lo, hi); | 346 | rdmsr(msr, lo, hi); |
336 | 347 | ||
337 | if (lo != msrwords[0] || hi != msrwords[1]) { | 348 | if (lo != msrwords[0] || hi != msrwords[1]) { |
338 | if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD && | ||
339 | (boot_cpu_data.x86 >= 0x0f && boot_cpu_data.x86 <= 0x11) && | ||
340 | ((msrwords[0] | msrwords[1]) & K8_MTRR_RDMEM_WRMEM_MASK)) | ||
341 | k8_enable_fixed_iorrs(); | ||
342 | mtrr_wrmsr(msr, msrwords[0], msrwords[1]); | 349 | mtrr_wrmsr(msr, msrwords[0], msrwords[1]); |
343 | *changed = true; | 350 | *changed = true; |
344 | } | 351 | } |
@@ -426,6 +433,8 @@ static int set_fixed_ranges(mtrr_type * frs) | |||
426 | bool changed = false; | 433 | bool changed = false; |
427 | int block=-1, range; | 434 | int block=-1, range; |
428 | 435 | ||
436 | k8_check_syscfg_dram_mod_en(); | ||
437 | |||
429 | while (fixed_range_blocks[++block].ranges) | 438 | while (fixed_range_blocks[++block].ranges) |
430 | for (range=0; range < fixed_range_blocks[block].ranges; range++) | 439 | for (range=0; range < fixed_range_blocks[block].ranges; range++) |
431 | set_fixed_range(fixed_range_blocks[block].base_msr + range, | 440 | set_fixed_range(fixed_range_blocks[block].base_msr + range, |