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 | |
| 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>
| -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, |
