Skip to content

Commit

Permalink
printk/nmi: flush NMI messages on the system panic
Browse files Browse the repository at this point in the history
In NMI context, printk() messages are stored into per-CPU buffers to
avoid a possible deadlock.  They are normally flushed to the main ring
buffer via an IRQ work.  But the work is never called when the system
calls panic() in the very same NMI handler.

This patch tries to flush NMI buffers before the crash dump is
generated.  In this case it does not risk a double release and bails out
when the logbuf_lock is already taken.  The aim is to get the messages
into the main ring buffer when possible.  It makes them better
accessible in the vmcore.

Then the patch tries to flush the buffers second time when other CPUs
are down.  It might be more aggressive and reset logbuf_lock.  The aim
is to get the messages available for the consequent kmsg_dump() and
console_flush_on_panic() calls.

The patch causes vprintk_emit() to be called even in NMI context again.
But it is done via printk_deferred() so that the console handling is
skipped.  Consoles use internal locks and we could not prevent a
deadlock easily.  They are explicitly called later when the crash dump
is not generated, see console_flush_on_panic().

Signed-off-by: Petr Mladek <[email protected]>
Cc: Benjamin Herrenschmidt <[email protected]>
Cc: Daniel Thompson <[email protected]>
Cc: David Miller <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Jan Kara <[email protected]>
Cc: Jiri Kosina <[email protected]>
Cc: Martin Schwidefsky <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Ralf Baechle <[email protected]>
Cc: Russell King <[email protected]>
Cc: Steven Rostedt <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
pmladek authored and torvalds committed May 21, 2016
1 parent 427934b commit cf9b110
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 3 deletions.
2 changes: 2 additions & 0 deletions include/linux/printk.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,13 @@ extern void printk_nmi_init(void);
extern void printk_nmi_enter(void);
extern void printk_nmi_exit(void);
extern void printk_nmi_flush(void);
extern void printk_nmi_flush_on_panic(void);
#else
static inline void printk_nmi_init(void) { }
static inline void printk_nmi_enter(void) { }
static inline void printk_nmi_exit(void) { }
static inline void printk_nmi_flush(void) { }
static inline void printk_nmi_flush_on_panic(void) { }
#endif /* PRINTK_NMI */

#ifdef CONFIG_PRINTK
Expand Down
1 change: 1 addition & 0 deletions kernel/kexec_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,7 @@ void crash_kexec(struct pt_regs *regs)
old_cpu = atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, this_cpu);
if (old_cpu == PANIC_CPU_INVALID) {
/* This is the 1st CPU which comes here, so go ahead. */
printk_nmi_flush_on_panic();
__crash_kexec(regs);

/*
Expand Down
6 changes: 5 additions & 1 deletion kernel/panic.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,10 @@ void panic(const char *fmt, ...)
*
* Bypass the panic_cpu check and call __crash_kexec directly.
*/
if (!crash_kexec_post_notifiers)
if (!crash_kexec_post_notifiers) {
printk_nmi_flush_on_panic();
__crash_kexec(NULL);
}

/*
* Note smp_send_stop is the usual smp shutdown function, which
Expand All @@ -176,6 +178,8 @@ void panic(const char *fmt, ...)
*/
atomic_notifier_call_chain(&panic_notifier_list, 0, buf);

/* Call flush even twice. It tries harder with a single online CPU */
printk_nmi_flush_on_panic();
kmsg_dump(KMSG_DUMP_PANIC);

/*
Expand Down
2 changes: 2 additions & 0 deletions kernel/printk/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ int __printf(1, 0) vprintk_default(const char *fmt, va_list args);

#ifdef CONFIG_PRINTK_NMI

extern raw_spinlock_t logbuf_lock;

/*
* printk() could not take logbuf_lock in NMI context. Instead,
* it temporary stores the strings into a per-CPU buffer.
Expand Down
39 changes: 38 additions & 1 deletion kernel/printk/nmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <linux/preempt.h>
#include <linux/spinlock.h>
#include <linux/debug_locks.h>
#include <linux/smp.h>
#include <linux/cpumask.h>
#include <linux/irq_work.h>
Expand Down Expand Up @@ -106,7 +107,16 @@ static void print_nmi_seq_line(struct nmi_seq_buf *s, int start, int end)
{
const char *buf = s->buffer + start;

printk("%.*s", (end - start) + 1, buf);
/*
* The buffers are flushed in NMI only on panic. The messages must
* go only into the ring buffer at this stage. Consoles will get
* explicitly called later when a crashdump is not generated.
*/
if (in_nmi())
printk_deferred("%.*s", (end - start) + 1, buf);
else
printk("%.*s", (end - start) + 1, buf);

}

/*
Expand Down Expand Up @@ -194,6 +204,33 @@ void printk_nmi_flush(void)
__printk_nmi_flush(&per_cpu(nmi_print_seq, cpu).work);
}

/**
* printk_nmi_flush_on_panic - flush all per-cpu nmi buffers when the system
* goes down.
*
* Similar to printk_nmi_flush() but it can be called even in NMI context when
* the system goes down. It does the best effort to get NMI messages into
* the main ring buffer.
*
* Note that it could try harder when there is only one CPU online.
*/
void printk_nmi_flush_on_panic(void)
{
/*
* Make sure that we could access the main ring buffer.
* Do not risk a double release when more CPUs are up.
*/
if (in_nmi() && raw_spin_is_locked(&logbuf_lock)) {
if (num_online_cpus() > 1)
return;

debug_locks_off();
raw_spin_lock_init(&logbuf_lock);
}

printk_nmi_flush();
}

void __init printk_nmi_init(void)
{
int cpu;
Expand Down
2 changes: 1 addition & 1 deletion kernel/printk/printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ __packed __aligned(4)
* within the scheduler's rq lock. It must be released before calling
* console_unlock() or anything else that might wake up a process.
*/
static DEFINE_RAW_SPINLOCK(logbuf_lock);
DEFINE_RAW_SPINLOCK(logbuf_lock);

#ifdef CONFIG_PRINTK
DECLARE_WAIT_QUEUE_HEAD(log_wait);
Expand Down

0 comments on commit cf9b110

Please sign in to comment.