x86: Stop MCEs and NMIs during code patching
authorAndi Kleen <ak@suse.de>
Sun, 22 Jul 2007 09:12:32 +0000 (11:12 +0200)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Sun, 22 Jul 2007 18:03:37 +0000 (11:03 -0700)
When a machine check or NMI occurs while multiple byte code is patched
the CPU could theoretically see an inconsistent instruction and crash.
Prevent this by temporarily disabling MCEs and returning early in the
NMI handler.

Based on discussion with Mathieu Desnoyers.

Cc: Mathieu Desnoyers <compudj@krystal.dyndns.org>
Cc: Jeremy Fitzhardinge <jeremy@goop.org>
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
arch/i386/kernel/alternative.c
arch/i386/kernel/cpu/mcheck/mce.c
arch/i386/kernel/traps.c
arch/x86_64/kernel/mce.c
arch/x86_64/kernel/nmi.c
include/asm-i386/mce.h
include/asm-i386/nmi.h
include/asm-x86_64/mce.h
include/asm-x86_64/nmi.h

index 206ea2ca63cc5beb221c238ff81172244de262b9..c3750c2c41137a694be9570443f59b379cc4cc4d 100644 (file)
@@ -8,6 +8,8 @@
 #include <asm/alternative.h>
 #include <asm/sections.h>
 #include <asm/pgtable.h>
+#include <asm/mce.h>
+#include <asm/nmi.h>
 
 #ifdef CONFIG_HOTPLUG_CPU
 static int smp_alt_once;
@@ -373,6 +375,14 @@ void __init alternative_instructions(void)
 {
        unsigned long flags;
 
+       /* The patching is not fully atomic, so try to avoid local interruptions
+          that might execute the to be patched code.
+          Other CPUs are not running. */
+       stop_nmi();
+#ifdef CONFIG_MCE
+       stop_mce();
+#endif
+
        local_irq_save(flags);
        apply_alternatives(__alt_instructions, __alt_instructions_end);
 
@@ -405,6 +415,11 @@ void __init alternative_instructions(void)
 #endif
        apply_paravirt(__parainstructions, __parainstructions_end);
        local_irq_restore(flags);
+
+       restart_nmi();
+#ifdef CONFIG_MCE
+       restart_mce();
+#endif
 }
 
 /*
index 56cd485b127ce59b2da500e9f176b83bb755b7b7..34c781eddee4cdc6fce3419366b63329965655a5 100644 (file)
@@ -60,6 +60,20 @@ void mcheck_init(struct cpuinfo_x86 *c)
        }
 }
 
+static unsigned long old_cr4 __initdata;
+
+void __init stop_mce(void)
+{
+       old_cr4 = read_cr4();
+       clear_in_cr4(X86_CR4_MCE);
+}
+
+void __init restart_mce(void)
+{
+       if (old_cr4 & X86_CR4_MCE)
+               set_in_cr4(X86_CR4_MCE);
+}
+
 static int __init mcheck_disable(char *str)
 {
        mce_disabled = 1;
index 438949da3b634d63e40503da9baba34fe00e130b..cfffe3dd9e838315089c9461a49b0fa2b6aef12b 100644 (file)
@@ -775,6 +775,8 @@ static __kprobes void default_do_nmi(struct pt_regs * regs)
        reassert_nmi();
 }
 
+static int ignore_nmis;
+
 fastcall __kprobes void do_nmi(struct pt_regs * regs, long error_code)
 {
        int cpu;
@@ -785,11 +787,24 @@ fastcall __kprobes void do_nmi(struct pt_regs * regs, long error_code)
 
        ++nmi_count(cpu);
 
-       default_do_nmi(regs);
+       if (!ignore_nmis)
+               default_do_nmi(regs);
 
        nmi_exit();
 }
 
+void stop_nmi(void)
+{
+       acpi_nmi_disable();
+       ignore_nmis++;
+}
+
+void restart_nmi(void)
+{
+       ignore_nmis--;
+       acpi_nmi_enable();
+}
+
 #ifdef CONFIG_KPROBES
 fastcall void __kprobes do_int3(struct pt_regs *regs, long error_code)
 {
index 4d8450ee3635e8ee587f5f639f7862b5a5155a74..a66d607f5b924e09d864120d1d656c3e1a1c2da0 100644 (file)
@@ -667,6 +667,20 @@ static struct miscdevice mce_log_device = {
        &mce_chrdev_ops,
 };
 
+static unsigned long old_cr4 __initdata;
+
+void __init stop_mce(void)
+{
+       old_cr4 = read_cr4();
+       clear_in_cr4(X86_CR4_MCE);
+}
+
+void __init restart_mce(void)
+{
+       if (old_cr4 & X86_CR4_MCE)
+               set_in_cr4(X86_CR4_MCE);
+}
+
 /* 
  * Old style boot options parsing. Only for compatibility. 
  */
index edbbc59b752334d570cb939c70ece3f43ca8ae42..cb8ee9d02f8682d4e52374aae1847a4daf03292a 100644 (file)
@@ -384,11 +384,14 @@ int __kprobes nmi_watchdog_tick(struct pt_regs * regs, unsigned reason)
        return rc;
 }
 
+static unsigned ignore_nmis;
+
 asmlinkage __kprobes void do_nmi(struct pt_regs * regs, long error_code)
 {
        nmi_enter();
        add_pda(__nmi_count,1);
-       default_do_nmi(regs);
+       if (!ignore_nmis)
+               default_do_nmi(regs);
        nmi_exit();
 }
 
@@ -401,6 +404,18 @@ int do_nmi_callback(struct pt_regs * regs, int cpu)
        return 0;
 }
 
+void stop_nmi(void)
+{
+       acpi_nmi_disable();
+       ignore_nmis++;
+}
+
+void restart_nmi(void)
+{
+       ignore_nmis--;
+       acpi_nmi_enable();
+}
+
 #ifdef CONFIG_SYSCTL
 
 static int unknown_nmi_panic_callback(struct pt_regs *regs, int cpu)
index b0a02ee34ffd74720747c295077720e86604933c..d56d89742e8fdcd0bfd96ccd1ff83292eea0bc99 100644 (file)
@@ -5,3 +5,7 @@ extern void mcheck_init(struct cpuinfo_x86 *c);
 #endif
 
 extern int mce_disabled;
+
+extern void stop_mce(void);
+extern void restart_mce(void);
+
index fb1e133efd9fecdc182d827ddd8c75841b8c6660..ff30c98f87b023290c8d1355f3873540e7d6c2ae 100644 (file)
@@ -57,5 +57,7 @@ unsigned lapic_adjust_nmi_hz(unsigned hz);
 int lapic_watchdog_ok(void);
 void disable_lapic_nmi_watchdog(void);
 void enable_lapic_nmi_watchdog(void);
+void stop_nmi(void);
+void restart_nmi(void);
 
 #endif /* ASM_NMI_H */
index 556be5563e308f2d80e7e67f65b4ab19b67b3b49..7bc030a1996de9c2ad53763eac202643e459a54b 100644 (file)
@@ -107,6 +107,9 @@ extern void do_machine_check(struct pt_regs *, long);
 
 extern int mce_notify_user(void);
 
+extern void stop_mce(void);
+extern void restart_mce(void);
+
 #endif
 
 #endif
index d0a7f53b1497cf65442c2162716608378589c467..5fb3c0de5cccba5a421a3820bb551619d489fad9 100644 (file)
@@ -88,5 +88,7 @@ unsigned lapic_adjust_nmi_hz(unsigned hz);
 int lapic_watchdog_ok(void);
 void disable_lapic_nmi_watchdog(void);
 void enable_lapic_nmi_watchdog(void);
+void stop_nmi(void);
+void restart_nmi(void);
 
 #endif /* ASM_NMI_H */