Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/nmi: Push duration printk() to irq context

Calling printk() from NMI context is bad (TM), so move it to IRQ
context.

In doing so we slightly change (probably wreck) the debugfs
nmi_longest_ns thingy, in that it doesn't update to reflect the
longest, nor does writing to it reset the count.

Signed-off-by: Peter Zijlstra <peterz@infradead.org>
Cc: Don Zickus <dzickus@redhat.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Link: http://lkml.kernel.org/n/tip-rdw0au56a5ymis1u8p48c12d@git.kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Peter Zijlstra and committed by
Ingo Molnar
e90c7853 6a02ad66

+27 -13
+3
arch/x86/include/asm/nmi.h
··· 1 1 #ifndef _ASM_X86_NMI_H 2 2 #define _ASM_X86_NMI_H 3 3 4 + #include <linux/irq_work.h> 4 5 #include <linux/pm.h> 5 6 #include <asm/irq.h> 6 7 #include <asm/io.h> ··· 39 38 struct nmiaction { 40 39 struct list_head list; 41 40 nmi_handler_t handler; 41 + u64 max_duration; 42 + struct irq_work irq_work; 42 43 unsigned long flags; 43 44 const char *name; 44 45 };
+24 -13
arch/x86/kernel/nmi.c
··· 87 87 #define nmi_to_desc(type) (&nmi_desc[type]) 88 88 89 89 static u64 nmi_longest_ns = 1 * NSEC_PER_MSEC; 90 + 90 91 static int __init nmi_warning_debugfs(void) 91 92 { 92 93 debugfs_create_u64("nmi_longest_ns", 0644, ··· 95 94 return 0; 96 95 } 97 96 fs_initcall(nmi_warning_debugfs); 97 + 98 + static void nmi_max_handler(struct irq_work *w) 99 + { 100 + struct nmiaction *a = container_of(w, struct nmiaction, irq_work); 101 + int remainder_ns, decimal_msecs; 102 + u64 whole_msecs = ACCESS_ONCE(a->max_duration); 103 + 104 + remainder_ns = do_div(whole_msecs, (1000 * 1000)); 105 + decimal_msecs = remainder_ns / 1000; 106 + 107 + printk_ratelimited(KERN_INFO 108 + "INFO: NMI handler (%ps) took too long to run: %lld.%03d msecs\n", 109 + a->handler, whole_msecs, decimal_msecs); 110 + } 98 111 99 112 static int __kprobes nmi_handle(unsigned int type, struct pt_regs *regs, bool b2b) 100 113 { ··· 125 110 * to handle those situations. 126 111 */ 127 112 list_for_each_entry_rcu(a, &desc->head, list) { 128 - u64 before, delta, whole_msecs; 129 - int remainder_ns, decimal_msecs, thishandled; 113 + int thishandled; 114 + u64 delta; 130 115 131 - before = sched_clock(); 116 + delta = sched_clock(); 132 117 thishandled = a->handler(type, regs); 133 118 handled += thishandled; 134 - delta = sched_clock() - before; 119 + delta = sched_clock() - delta; 135 120 trace_nmi_handler(a->handler, (int)delta, thishandled); 136 121 137 - if (delta < nmi_longest_ns) 122 + if (delta < nmi_longest_ns || delta < a->max_duration) 138 123 continue; 139 124 140 - nmi_longest_ns = delta; 141 - whole_msecs = delta; 142 - remainder_ns = do_div(whole_msecs, (1000 * 1000)); 143 - decimal_msecs = remainder_ns / 1000; 144 - printk_ratelimited(KERN_INFO 145 - "INFO: NMI handler (%ps) took too long to run: " 146 - "%lld.%03d msecs\n", a->handler, whole_msecs, 147 - decimal_msecs); 125 + a->max_duration = delta; 126 + irq_work_queue(&a->irq_work); 148 127 } 149 128 150 129 rcu_read_unlock(); ··· 154 145 155 146 if (!action->handler) 156 147 return -EINVAL; 148 + 149 + init_irq_work(&action->irq_work, nmi_max_handler); 157 150 158 151 spin_lock_irqsave(&desc->lock, flags); 159 152