Skip to content

Commit

Permalink
panic, x86: Allow CPUs to save registers even if looping in NMI context
Browse files Browse the repository at this point in the history
Currently, kdump_nmi_shootdown_cpus(), a subroutine of crash_kexec(),
sends an NMI IPI to CPUs which haven't called panic() to stop them,
save their register information and do some cleanups for crash dumping.
However, if such a CPU is infinitely looping in NMI context, we fail to
save its register information into the crash dump.

For example, this can happen when unknown NMIs are broadcast to all
CPUs as follows:

  CPU 0                             CPU 1
  ===========================       ==========================
  receive an unknown NMI
  unknown_nmi_error()
    panic()                         receive an unknown NMI
      spin_trylock(&panic_lock)     unknown_nmi_error()
      crash_kexec()                   panic()
                                        spin_trylock(&panic_lock)
                                        panic_smp_self_stop()
                                          infinite loop
        kdump_nmi_shootdown_cpus()
          issue NMI IPI -----------> blocked until IRET
                                          infinite loop...

Here, since CPU 1 is in NMI context, the second NMI from CPU 0 is
blocked until CPU 1 executes IRET. However, CPU 1 never executes IRET,
so the NMI is not handled and the callback function to save registers is
never called.

In practice, this can happen on some servers which broadcast NMIs to all
CPUs when the NMI button is pushed.

To save registers in this case, we need to:

  a) Return from NMI handler instead of looping infinitely
  or
  b) Call the callback function directly from the infinite loop

Inherently, a) is risky because NMI is also used to prevent corrupted
data from being propagated to devices.  So, we chose b).

This patch does the following:

1. Move the infinite looping of CPUs which haven't called panic() in NMI
   context (actually done by panic_smp_self_stop()) outside of panic() to
   enable us to refer pt_regs. Please note that panic_smp_self_stop() is
   still used for normal context.

2. Call a callback of kdump_nmi_shootdown_cpus() directly to save
   registers and do some cleanups after setting waiting_for_crash_ipi which
   is used for counting down the number of CPUs which handled the callback

Signed-off-by: Hidehiro Kawai <[email protected]>
Acked-by: Michal Hocko <[email protected]>
Cc: Aaron Tomlin <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Baoquan He <[email protected]>
Cc: Chris Metcalf <[email protected]>
Cc: Dave Young <[email protected]>
Cc: David Hildenbrand <[email protected]>
Cc: Don Zickus <[email protected]>
Cc: Eric Biederman <[email protected]>
Cc: Frederic Weisbecker <[email protected]>
Cc: Gobinda Charan Maji <[email protected]>
Cc: HATAYAMA Daisuke <[email protected]>
Cc: Hidehiro Kawai <[email protected]>
Cc: "H. Peter Anvin" <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Javi Merino <[email protected]>
Cc: Jiang Liu <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: lkml <[email protected]>
Cc: Masami Hiramatsu <[email protected]>
Cc: Michal Nazarewicz <[email protected]>
Cc: Nicolas Iooss <[email protected]>
Cc: Oleg Nesterov <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Prarit Bhargava <[email protected]>
Cc: Rasmus Villemoes <[email protected]>
Cc: Seth Jennings <[email protected]>
Cc: Stefan Lippers-Hollmann <[email protected]>
Cc: Steven Rostedt <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Ulrich Obergfell <[email protected]>
Cc: Vitaly Kuznetsov <[email protected]>
Cc: Vivek Goyal <[email protected]>
Cc: Yasuaki Ishimatsu <[email protected]>
Link: http://lkml.kernel.org/r/20151210014628.25437.75256.stgit@softrs
[ Cleanup comments, fixup formatting. ]
Signed-off-by: Borislav Petkov <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
  • Loading branch information
Hidehiro Kawai authored and KAGA-KOKO committed Dec 19, 2015
1 parent 1717f20 commit 58c5661
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 8 deletions.
6 changes: 3 additions & 3 deletions arch/x86/kernel/nmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ pci_serr_error(unsigned char reason, struct pt_regs *regs)
#endif

if (panic_on_unrecovered_nmi)
nmi_panic("NMI: Not continuing");
nmi_panic(regs, "NMI: Not continuing");

pr_emerg("Dazed and confused, but trying to continue\n");

Expand All @@ -256,7 +256,7 @@ io_check_error(unsigned char reason, struct pt_regs *regs)
show_regs(regs);

if (panic_on_io_nmi) {
nmi_panic("NMI IOCK error: Not continuing");
nmi_panic(regs, "NMI IOCK error: Not continuing");

/*
* If we end up here, it means we have received an NMI while
Expand Down Expand Up @@ -305,7 +305,7 @@ unknown_nmi_error(unsigned char reason, struct pt_regs *regs)

pr_emerg("Do you have a strange power saving mode enabled?\n");
if (unknown_nmi_panic || panic_on_unrecovered_nmi)
nmi_panic("NMI: Not continuing");
nmi_panic(regs, "NMI: Not continuing");

pr_emerg("Dazed and confused, but trying to continue\n");
}
Expand Down
20 changes: 20 additions & 0 deletions arch/x86/kernel/reboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ static int crashing_cpu;
static nmi_shootdown_cb shootdown_callback;

static atomic_t waiting_for_crash_ipi;
static int crash_ipi_issued;

static int crash_nmi_callback(unsigned int val, struct pt_regs *regs)
{
Expand Down Expand Up @@ -780,6 +781,9 @@ void nmi_shootdown_cpus(nmi_shootdown_cb callback)

smp_send_nmi_allbutself();

/* Kick CPUs looping in NMI context. */
WRITE_ONCE(crash_ipi_issued, 1);

msecs = 1000; /* Wait at most a second for the other cpus to stop */
while ((atomic_read(&waiting_for_crash_ipi) > 0) && msecs) {
mdelay(1);
Expand All @@ -788,6 +792,22 @@ void nmi_shootdown_cpus(nmi_shootdown_cb callback)

/* Leave the nmi callback set */
}

/* Override the weak function in kernel/panic.c */
void nmi_panic_self_stop(struct pt_regs *regs)
{
while (1) {
/*
* Wait for the crash dumping IPI to be issued, and then
* call its callback directly.
*/
if (READ_ONCE(crash_ipi_issued))
crash_nmi_callback(0, regs); /* Don't return */

cpu_relax();
}
}

#else /* !CONFIG_SMP */
void nmi_shootdown_cpus(nmi_shootdown_cb callback)
{
Expand Down
16 changes: 12 additions & 4 deletions include/linux/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ extern long (*panic_blink)(int state);
__printf(1, 2)
void panic(const char *fmt, ...)
__noreturn __cold;
void nmi_panic_self_stop(struct pt_regs *);
extern void oops_enter(void);
extern void oops_exit(void);
void print_oops_end_marker(void);
Expand Down Expand Up @@ -455,14 +456,21 @@ extern atomic_t panic_cpu;

/*
* A variant of panic() called from NMI context. We return if we've already
* panicked on this CPU.
* panicked on this CPU. If another CPU already panicked, loop in
* nmi_panic_self_stop() which can provide architecture dependent code such
* as saving register state for crash dump.
*/
#define nmi_panic(fmt, ...) \
#define nmi_panic(regs, fmt, ...) \
do { \
int cpu = raw_smp_processor_id(); \
int old_cpu, cpu; \
\
if (atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, cpu) != cpu) \
cpu = raw_smp_processor_id(); \
old_cpu = atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, cpu); \
\
if (old_cpu == PANIC_CPU_INVALID) \
panic(fmt, ##__VA_ARGS__); \
else if (old_cpu != cpu) \
nmi_panic_self_stop(regs); \
} while (0)

/*
Expand Down
9 changes: 9 additions & 0 deletions kernel/panic.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ void __weak panic_smp_self_stop(void)
cpu_relax();
}

/*
* Stop ourselves in NMI context if another CPU has already panicked. Arch code
* may override this to prepare for crash dumping, e.g. save regs info.
*/
void __weak nmi_panic_self_stop(struct pt_regs *regs)
{
panic_smp_self_stop();
}

atomic_t panic_cpu = ATOMIC_INIT(PANIC_CPU_INVALID);

/**
Expand Down
2 changes: 1 addition & 1 deletion kernel/watchdog.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ static void watchdog_overflow_callback(struct perf_event *event,
trigger_allbutself_cpu_backtrace();

if (hardlockup_panic)
nmi_panic("Hard LOCKUP");
nmi_panic(regs, "Hard LOCKUP");

__this_cpu_write(hard_watchdog_warn, true);
return;
Expand Down

0 comments on commit 58c5661

Please sign in to comment.