x86/crash: Disable virt in core NMI crash handler to avoid double shootdown
[sfrench/cifs-2.6.git] / arch / x86 / kernel / reboot.c
index c3636ea4aa71f47e79af3699a657d7586cd85077..374c14b3a9bf901839f325c1eca13afe998f4cec 100644 (file)
@@ -528,10 +528,7 @@ static inline void kb_wait(void)
        }
 }
 
-static void vmxoff_nmi(int cpu, struct pt_regs *regs)
-{
-       cpu_emergency_vmxoff();
-}
+static inline void nmi_shootdown_cpus_on_restart(void);
 
 /* Use NMIs as IPIs to tell all CPUs to disable virtualization */
 static void emergency_vmx_disable_all(void)
@@ -554,7 +551,7 @@ static void emergency_vmx_disable_all(void)
                __cpu_emergency_vmxoff();
 
                /* Halt and exit VMX root operation on the other CPUs. */
-               nmi_shootdown_cpus(vmxoff_nmi);
+               nmi_shootdown_cpus_on_restart();
        }
 }
 
@@ -795,6 +792,17 @@ void machine_crash_shutdown(struct pt_regs *regs)
 /* This is the CPU performing the emergency shutdown work. */
 int crashing_cpu = -1;
 
+/*
+ * Disable virtualization, i.e. VMX or SVM, to ensure INIT is recognized during
+ * reboot.  VMX blocks INIT if the CPU is post-VMXON, and SVM blocks INIT if
+ * GIF=0, i.e. if the crash occurred between CLGI and STGI.
+ */
+void cpu_emergency_disable_virtualization(void)
+{
+       cpu_emergency_vmxoff();
+       cpu_emergency_svm_disable();
+}
+
 #if defined(CONFIG_SMP)
 
 static nmi_shootdown_cb shootdown_callback;
@@ -817,7 +825,14 @@ static int crash_nmi_callback(unsigned int val, struct pt_regs *regs)
                return NMI_HANDLED;
        local_irq_disable();
 
-       shootdown_callback(cpu, regs);
+       if (shootdown_callback)
+               shootdown_callback(cpu, regs);
+
+       /*
+        * Prepare the CPU for reboot _after_ invoking the callback so that the
+        * callback can safely use virtualization instructions, e.g. VMCLEAR.
+        */
+       cpu_emergency_disable_virtualization();
 
        atomic_dec(&waiting_for_crash_ipi);
        /* Assume hlt works */
@@ -828,18 +843,32 @@ static int crash_nmi_callback(unsigned int val, struct pt_regs *regs)
        return NMI_HANDLED;
 }
 
-/*
- * Halt all other CPUs, calling the specified function on each of them
+/**
+ * nmi_shootdown_cpus - Stop other CPUs via NMI
+ * @callback:  Optional callback to be invoked from the NMI handler
+ *
+ * The NMI handler on the remote CPUs invokes @callback, if not
+ * NULL, first and then disables virtualization to ensure that
+ * INIT is recognized during reboot.
  *
- * This function can be used to halt all other CPUs on crash
- * or emergency reboot time. The function passed as parameter
- * will be called inside a NMI handler on all CPUs.
+ * nmi_shootdown_cpus() can only be invoked once. After the first
+ * invocation all other CPUs are stuck in crash_nmi_callback() and
+ * cannot respond to a second NMI.
  */
 void nmi_shootdown_cpus(nmi_shootdown_cb callback)
 {
        unsigned long msecs;
+
        local_irq_disable();
 
+       /*
+        * Avoid certain doom if a shootdown already occurred; re-registering
+        * the NMI handler will cause list corruption, modifying the callback
+        * will do who knows what, etc...
+        */
+       if (WARN_ON_ONCE(crash_ipi_issued))
+               return;
+
        /* Make a note of crashing cpu. Will be used in NMI callback. */
        crashing_cpu = safe_smp_processor_id();
 
@@ -867,7 +896,17 @@ void nmi_shootdown_cpus(nmi_shootdown_cb callback)
                msecs--;
        }
 
-       /* Leave the nmi callback set */
+       /*
+        * Leave the nmi callback set, shootdown is a one-time thing.  Clearing
+        * the callback could result in a NULL pointer dereference if a CPU
+        * (finally) responds after the timeout expires.
+        */
+}
+
+static inline void nmi_shootdown_cpus_on_restart(void)
+{
+       if (!crash_ipi_issued)
+               nmi_shootdown_cpus(NULL);
 }
 
 /*
@@ -897,6 +936,8 @@ void nmi_shootdown_cpus(nmi_shootdown_cb callback)
        /* No other CPUs to shoot down */
 }
 
+static inline void nmi_shootdown_cpus_on_restart(void) { }
+
 void run_crash_ipi_callback(struct pt_regs *regs)
 {
 }