mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	powerpc/64s: implement arch-specific hardlockup watchdog
Implement an arch-speicfic watchdog rather than use the perf-based hardlockup detector. The new watchdog takes the soft-NMI directly, rather than going through perf. Perf interrupts are to be made maskable in future, so that would prevent the perf detector from working in those regions. Additionally, implement a SMP based detector where all CPUs watch one another by pinging a shared cpumask. This is because powerpc Book3S does not have a true periodic local NMI, but some platforms do implement a true NMI IPI. If a CPU is stuck with interrupts hard disabled, the soft-NMI watchdog does not work, but the SMP watchdog will. Even on platforms without a true NMI IPI to get a good trace from the stuck CPU, other CPUs will notice the lockup sufficiently to report it and panic. [npiggin@gmail.com: honor watchdog disable at boot/hotplug] Link: http://lkml.kernel.org/r/20170621001346.5bb337c9@roar.ozlabs.ibm.com [npiggin@gmail.com: fix false positive warning at CPU unplug] Link: http://lkml.kernel.org/r/20170630080740.20766-1-npiggin@gmail.com [akpm@linux-foundation.org: coding-style fixes] Link: http://lkml.kernel.org/r/20170616065715.18390-6-npiggin@gmail.com Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Reviewed-by: Don Zickus <dzickus@redhat.com> Tested-by: Babu Moger <babu.moger@oracle.com> [sparc] Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
		
							parent
							
								
									a10a842ff8
								
							
						
					
					
						commit
						2104180a53
					
				
					 9 changed files with 458 additions and 25 deletions
				
			
		| 
						 | 
					@ -82,7 +82,7 @@ config NR_IRQS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config NMI_IPI
 | 
					config NMI_IPI
 | 
				
			||||||
	bool
 | 
						bool
 | 
				
			||||||
	depends on SMP && (DEBUGGER || KEXEC_CORE)
 | 
						depends on SMP && (DEBUGGER || KEXEC_CORE || HARDLOCKUP_DETECTOR)
 | 
				
			||||||
	default y
 | 
						default y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config STACKTRACE_SUPPORT
 | 
					config STACKTRACE_SUPPORT
 | 
				
			||||||
| 
						 | 
					@ -192,12 +192,13 @@ config PPC
 | 
				
			||||||
	select HAVE_MEMBLOCK
 | 
						select HAVE_MEMBLOCK
 | 
				
			||||||
	select HAVE_MEMBLOCK_NODE_MAP
 | 
						select HAVE_MEMBLOCK_NODE_MAP
 | 
				
			||||||
	select HAVE_MOD_ARCH_SPECIFIC
 | 
						select HAVE_MOD_ARCH_SPECIFIC
 | 
				
			||||||
	select HAVE_NMI				if PERF_EVENTS
 | 
						select HAVE_NMI				if PERF_EVENTS || (PPC64 && PPC_BOOK3S)
 | 
				
			||||||
 | 
						select HAVE_HARDLOCKUP_DETECTOR_ARCH	if (PPC64 && PPC_BOOK3S)
 | 
				
			||||||
	select HAVE_OPROFILE
 | 
						select HAVE_OPROFILE
 | 
				
			||||||
	select HAVE_OPTPROBES			if PPC64
 | 
						select HAVE_OPTPROBES			if PPC64
 | 
				
			||||||
	select HAVE_PERF_EVENTS
 | 
						select HAVE_PERF_EVENTS
 | 
				
			||||||
	select HAVE_PERF_EVENTS_NMI		if PPC64
 | 
						select HAVE_PERF_EVENTS_NMI		if PPC64
 | 
				
			||||||
	select HAVE_HARDLOCKUP_DETECTOR_PERF	if HAVE_PERF_EVENTS_NMI
 | 
						select HAVE_HARDLOCKUP_DETECTOR_PERF	if HAVE_PERF_EVENTS_NMI && !HAVE_HARDLOCKUP_DETECTOR_ARCH
 | 
				
			||||||
	select HAVE_PERF_REGS
 | 
						select HAVE_PERF_REGS
 | 
				
			||||||
	select HAVE_PERF_USER_STACK_DUMP
 | 
						select HAVE_PERF_USER_STACK_DUMP
 | 
				
			||||||
	select HAVE_RCU_TABLE_FREE		if SMP
 | 
						select HAVE_RCU_TABLE_FREE		if SMP
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,15 @@
 | 
				
			||||||
#ifndef _ASM_NMI_H
 | 
					#ifndef _ASM_NMI_H
 | 
				
			||||||
#define _ASM_NMI_H
 | 
					#define _ASM_NMI_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_HARDLOCKUP_DETECTOR
 | 
				
			||||||
 | 
					extern void arch_touch_nmi_watchdog(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern void arch_trigger_cpumask_backtrace(const cpumask_t *mask,
 | 
				
			||||||
 | 
										   bool exclude_self);
 | 
				
			||||||
 | 
					#define arch_trigger_cpumask_backtrace arch_trigger_cpumask_backtrace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static inline void arch_touch_nmi_watchdog(void) {}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* _ASM_NMI_H */
 | 
					#endif /* _ASM_NMI_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,8 @@ struct smp_ops_t {
 | 
				
			||||||
	int   (*cpu_bootable)(unsigned int nr);
 | 
						int   (*cpu_bootable)(unsigned int nr);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern void smp_flush_nmi_ipi(u64 delay_us);
 | 
				
			||||||
 | 
					extern int smp_send_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us);
 | 
				
			||||||
extern void smp_send_debugger_break(void);
 | 
					extern void smp_send_debugger_break(void);
 | 
				
			||||||
extern void start_secondary_resume(void);
 | 
					extern void start_secondary_resume(void);
 | 
				
			||||||
extern void smp_generic_give_timebase(void);
 | 
					extern void smp_generic_give_timebase(void);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,7 @@ obj-$(CONFIG_PPC64)		+= setup_64.o sys_ppc32.o \
 | 
				
			||||||
				   signal_64.o ptrace32.o \
 | 
									   signal_64.o ptrace32.o \
 | 
				
			||||||
				   paca.o nvram_64.o firmware.o
 | 
									   paca.o nvram_64.o firmware.o
 | 
				
			||||||
obj-$(CONFIG_VDSO32)		+= vdso32/
 | 
					obj-$(CONFIG_VDSO32)		+= vdso32/
 | 
				
			||||||
 | 
					obj-$(CONFIG_HARDLOCKUP_DETECTOR)	+= watchdog.o
 | 
				
			||||||
obj-$(CONFIG_HAVE_HW_BREAKPOINT)	+= hw_breakpoint.o
 | 
					obj-$(CONFIG_HAVE_HW_BREAKPOINT)	+= hw_breakpoint.o
 | 
				
			||||||
obj-$(CONFIG_PPC_BOOK3S_64)	+= cpu_setup_ppc970.o cpu_setup_pa6t.o
 | 
					obj-$(CONFIG_PPC_BOOK3S_64)	+= cpu_setup_ppc970.o cpu_setup_pa6t.o
 | 
				
			||||||
obj-$(CONFIG_PPC_BOOK3S_64)	+= cpu_setup_power.o
 | 
					obj-$(CONFIG_PPC_BOOK3S_64)	+= cpu_setup_power.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1314,6 +1314,31 @@ EXC_REAL_NONE(0x1800, 0x100)
 | 
				
			||||||
EXC_VIRT_NONE(0x5800, 0x100)
 | 
					EXC_VIRT_NONE(0x5800, 0x100)
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(CONFIG_HARDLOCKUP_DETECTOR) && defined(CONFIG_HAVE_HARDLOCKUP_DETECTOR_ARCH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MASKED_DEC_HANDLER_LABEL 3f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MASKED_DEC_HANDLER(_H)				\
 | 
				
			||||||
 | 
					3: /* soft-nmi */					\
 | 
				
			||||||
 | 
						std	r12,PACA_EXGEN+EX_R12(r13);		\
 | 
				
			||||||
 | 
						GET_SCRATCH0(r10);				\
 | 
				
			||||||
 | 
						std	r10,PACA_EXGEN+EX_R13(r13);		\
 | 
				
			||||||
 | 
						EXCEPTION_PROLOG_PSERIES_1(soft_nmi_common, _H)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXC_COMMON_BEGIN(soft_nmi_common)
 | 
				
			||||||
 | 
						mr	r10,r1
 | 
				
			||||||
 | 
						ld	r1,PACAEMERGSP(r13)
 | 
				
			||||||
 | 
						ld	r1,PACA_NMI_EMERG_SP(r13)
 | 
				
			||||||
 | 
						subi	r1,r1,INT_FRAME_SIZE
 | 
				
			||||||
 | 
						EXCEPTION_COMMON_NORET_STACK(PACA_EXGEN, 0x900,
 | 
				
			||||||
 | 
								system_reset, soft_nmi_interrupt,
 | 
				
			||||||
 | 
								ADD_NVGPRS;ADD_RECONCILE)
 | 
				
			||||||
 | 
						b	ret_from_except
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#define MASKED_DEC_HANDLER_LABEL 2f /* normal return */
 | 
				
			||||||
 | 
					#define MASKED_DEC_HANDLER(_H)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * An interrupt came in while soft-disabled. We set paca->irq_happened, then:
 | 
					 * An interrupt came in while soft-disabled. We set paca->irq_happened, then:
 | 
				
			||||||
| 
						 | 
					@ -1336,7 +1361,7 @@ masked_##_H##interrupt:					\
 | 
				
			||||||
	lis	r10,0x7fff;				\
 | 
						lis	r10,0x7fff;				\
 | 
				
			||||||
	ori	r10,r10,0xffff;				\
 | 
						ori	r10,r10,0xffff;				\
 | 
				
			||||||
	mtspr	SPRN_DEC,r10;				\
 | 
						mtspr	SPRN_DEC,r10;				\
 | 
				
			||||||
	b	2f;					\
 | 
						b	MASKED_DEC_HANDLER_LABEL;		\
 | 
				
			||||||
1:	cmpwi	r10,PACA_IRQ_DBELL;			\
 | 
					1:	cmpwi	r10,PACA_IRQ_DBELL;			\
 | 
				
			||||||
	beq	2f;					\
 | 
						beq	2f;					\
 | 
				
			||||||
	cmpwi	r10,PACA_IRQ_HMI;			\
 | 
						cmpwi	r10,PACA_IRQ_HMI;			\
 | 
				
			||||||
| 
						 | 
					@ -1351,7 +1376,8 @@ masked_##_H##interrupt:					\
 | 
				
			||||||
	ld	r11,PACA_EXGEN+EX_R11(r13);		\
 | 
						ld	r11,PACA_EXGEN+EX_R11(r13);		\
 | 
				
			||||||
	GET_SCRATCH0(r13);				\
 | 
						GET_SCRATCH0(r13);				\
 | 
				
			||||||
	##_H##rfid;					\
 | 
						##_H##rfid;					\
 | 
				
			||||||
	b	.
 | 
						b	.;					\
 | 
				
			||||||
 | 
						MASKED_DEC_HANDLER(_H)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Real mode exceptions actually use this too, but alternate
 | 
					 * Real mode exceptions actually use this too, but alternate
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@
 | 
				
			||||||
#include <linux/kvm_para.h>
 | 
					#include <linux/kvm_para.h>
 | 
				
			||||||
#include <linux/slab.h>
 | 
					#include <linux/slab.h>
 | 
				
			||||||
#include <linux/of.h>
 | 
					#include <linux/of.h>
 | 
				
			||||||
 | 
					#include <linux/nmi.h> /* hardlockup_detector_disable() */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <asm/reg.h>
 | 
					#include <asm/reg.h>
 | 
				
			||||||
#include <asm/sections.h>
 | 
					#include <asm/sections.h>
 | 
				
			||||||
| 
						 | 
					@ -718,6 +719,12 @@ static __init void kvm_free_tmp(void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __init kvm_guest_init(void)
 | 
					static int __init kvm_guest_init(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * The hardlockup detector is likely to get false positives in
 | 
				
			||||||
 | 
						 * KVM guests, so disable it by default.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						hardlockup_detector_disable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!kvm_para_available())
 | 
						if (!kvm_para_available())
 | 
				
			||||||
		goto free_tmp;
 | 
							goto free_tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -751,22 +751,3 @@ unsigned long memory_block_size_bytes(void)
 | 
				
			||||||
struct ppc_pci_io ppc_pci_io;
 | 
					struct ppc_pci_io ppc_pci_io;
 | 
				
			||||||
EXPORT_SYMBOL(ppc_pci_io);
 | 
					EXPORT_SYMBOL(ppc_pci_io);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef CONFIG_HARDLOCKUP_DETECTOR_PERF
 | 
					 | 
				
			||||||
u64 hw_nmi_get_sample_period(int watchdog_thresh)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	return ppc_proc_freq * watchdog_thresh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * The hardlockup detector breaks PMU event based branches and is likely
 | 
					 | 
				
			||||||
 * to get false positives in KVM guests, so disable it by default.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
static int __init disable_hardlockup_detector(void)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	hardlockup_detector_disable();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
early_initcall(disable_hardlockup_detector);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -435,13 +435,31 @@ static void do_smp_send_nmi_ipi(int cpu)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void smp_flush_nmi_ipi(u64 delay_us)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nmi_ipi_lock_start(&flags);
 | 
				
			||||||
 | 
						while (nmi_ipi_busy_count) {
 | 
				
			||||||
 | 
							nmi_ipi_unlock_end(&flags);
 | 
				
			||||||
 | 
							udelay(1);
 | 
				
			||||||
 | 
							if (delay_us) {
 | 
				
			||||||
 | 
								delay_us--;
 | 
				
			||||||
 | 
								if (!delay_us)
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nmi_ipi_lock_start(&flags);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nmi_ipi_unlock_end(&flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * - cpu is the target CPU (must not be this CPU), or NMI_IPI_ALL_OTHERS.
 | 
					 * - cpu is the target CPU (must not be this CPU), or NMI_IPI_ALL_OTHERS.
 | 
				
			||||||
 * - fn is the target callback function.
 | 
					 * - fn is the target callback function.
 | 
				
			||||||
 * - delay_us > 0 is the delay before giving up waiting for targets to
 | 
					 * - delay_us > 0 is the delay before giving up waiting for targets to
 | 
				
			||||||
 *   enter the handler, == 0 specifies indefinite delay.
 | 
					 *   enter the handler, == 0 specifies indefinite delay.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
static int smp_send_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us)
 | 
					int smp_send_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned long flags;
 | 
						unsigned long flags;
 | 
				
			||||||
	int me = raw_smp_processor_id();
 | 
						int me = raw_smp_processor_id();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										386
									
								
								arch/powerpc/kernel/watchdog.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								arch/powerpc/kernel/watchdog.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,386 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Watchdog support on powerpc systems.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright 2017, IBM Corporation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This uses code from arch/sparc/kernel/nmi.c and kernel/watchdog.c
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/param.h>
 | 
				
			||||||
 | 
					#include <linux/init.h>
 | 
				
			||||||
 | 
					#include <linux/percpu.h>
 | 
				
			||||||
 | 
					#include <linux/cpu.h>
 | 
				
			||||||
 | 
					#include <linux/nmi.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/export.h>
 | 
				
			||||||
 | 
					#include <linux/kprobes.h>
 | 
				
			||||||
 | 
					#include <linux/hardirq.h>
 | 
				
			||||||
 | 
					#include <linux/reboot.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/kdebug.h>
 | 
				
			||||||
 | 
					#include <linux/sched/debug.h>
 | 
				
			||||||
 | 
					#include <linux/delay.h>
 | 
				
			||||||
 | 
					#include <linux/smp.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <asm/paca.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * The watchdog has a simple timer that runs on each CPU, once per timer
 | 
				
			||||||
 | 
					 * period. This is the heartbeat.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Then there are checks to see if the heartbeat has not triggered on a CPU
 | 
				
			||||||
 | 
					 * for the panic timeout period. Currently the watchdog only supports an
 | 
				
			||||||
 | 
					 * SMP check, so the heartbeat only turns on when we have 2 or more CPUs.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is not an NMI watchdog, but Linux uses that name for a generic
 | 
				
			||||||
 | 
					 * watchdog in some cases, so NMI gets used in some places.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static cpumask_t wd_cpus_enabled __read_mostly;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static u64 wd_panic_timeout_tb __read_mostly; /* timebase ticks until panic */
 | 
				
			||||||
 | 
					static u64 wd_smp_panic_timeout_tb __read_mostly; /* panic other CPUs */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static u64 wd_timer_period_ms __read_mostly;  /* interval between heartbeat */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static DEFINE_PER_CPU(struct timer_list, wd_timer);
 | 
				
			||||||
 | 
					static DEFINE_PER_CPU(u64, wd_timer_tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * These are for the SMP checker. CPUs clear their pending bit in their
 | 
				
			||||||
 | 
					 * heartbeat. If the bitmask becomes empty, the time is noted and the
 | 
				
			||||||
 | 
					 * bitmask is refilled.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * All CPUs clear their bit in the pending mask every timer period.
 | 
				
			||||||
 | 
					 * Once all have cleared, the time is noted and the bits are reset.
 | 
				
			||||||
 | 
					 * If the time since all clear was greater than the panic timeout,
 | 
				
			||||||
 | 
					 * we can panic with the list of stuck CPUs.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This will work best with NMI IPIs for crash code so the stuck CPUs
 | 
				
			||||||
 | 
					 * can be pulled out to get their backtraces.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static unsigned long __wd_smp_lock;
 | 
				
			||||||
 | 
					static cpumask_t wd_smp_cpus_pending;
 | 
				
			||||||
 | 
					static cpumask_t wd_smp_cpus_stuck;
 | 
				
			||||||
 | 
					static u64 wd_smp_last_reset_tb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline void wd_smp_lock(unsigned long *flags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Avoid locking layers if possible.
 | 
				
			||||||
 | 
						 * This may be called from low level interrupt handlers at some
 | 
				
			||||||
 | 
						 * point in future.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						local_irq_save(*flags);
 | 
				
			||||||
 | 
						while (unlikely(test_and_set_bit_lock(0, &__wd_smp_lock)))
 | 
				
			||||||
 | 
							cpu_relax();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline void wd_smp_unlock(unsigned long *flags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						clear_bit_unlock(0, &__wd_smp_lock);
 | 
				
			||||||
 | 
						local_irq_restore(*flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void wd_lockup_ipi(struct pt_regs *regs)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						pr_emerg("Watchdog CPU:%d Hard LOCKUP\n", raw_smp_processor_id());
 | 
				
			||||||
 | 
						print_modules();
 | 
				
			||||||
 | 
						print_irqtrace_events(current);
 | 
				
			||||||
 | 
						if (regs)
 | 
				
			||||||
 | 
							show_regs(regs);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							dump_stack();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (hardlockup_panic)
 | 
				
			||||||
 | 
							nmi_panic(regs, "Hard LOCKUP");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void set_cpu_stuck(int cpu, u64 tb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						cpumask_set_cpu(cpu, &wd_smp_cpus_stuck);
 | 
				
			||||||
 | 
						cpumask_clear_cpu(cpu, &wd_smp_cpus_pending);
 | 
				
			||||||
 | 
						if (cpumask_empty(&wd_smp_cpus_pending)) {
 | 
				
			||||||
 | 
							wd_smp_last_reset_tb = tb;
 | 
				
			||||||
 | 
							cpumask_andnot(&wd_smp_cpus_pending,
 | 
				
			||||||
 | 
									&wd_cpus_enabled,
 | 
				
			||||||
 | 
									&wd_smp_cpus_stuck);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void watchdog_smp_panic(int cpu, u64 tb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						int c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wd_smp_lock(&flags);
 | 
				
			||||||
 | 
						/* Double check some things under lock */
 | 
				
			||||||
 | 
						if ((s64)(tb - wd_smp_last_reset_tb) < (s64)wd_smp_panic_timeout_tb)
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						if (cpumask_test_cpu(cpu, &wd_smp_cpus_pending))
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						if (cpumask_weight(&wd_smp_cpus_pending) == 0)
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr_emerg("Watchdog CPU:%d detected Hard LOCKUP other CPUS:%*pbl\n",
 | 
				
			||||||
 | 
								cpu, cpumask_pr_args(&wd_smp_cpus_pending));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Try to trigger the stuck CPUs.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						for_each_cpu(c, &wd_smp_cpus_pending) {
 | 
				
			||||||
 | 
							if (c == cpu)
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							smp_send_nmi_ipi(c, wd_lockup_ipi, 1000000);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						smp_flush_nmi_ipi(1000000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Take the stuck CPU out of the watch group */
 | 
				
			||||||
 | 
						for_each_cpu(c, &wd_smp_cpus_pending)
 | 
				
			||||||
 | 
							set_cpu_stuck(c, tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						wd_smp_unlock(&flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						printk_safe_flush();
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * printk_safe_flush() seems to require another print
 | 
				
			||||||
 | 
						 * before anything actually goes out to console.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (sysctl_hardlockup_all_cpu_backtrace)
 | 
				
			||||||
 | 
							trigger_allbutself_cpu_backtrace();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (hardlockup_panic)
 | 
				
			||||||
 | 
							nmi_panic(NULL, "Hard LOCKUP");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void wd_smp_clear_cpu_pending(int cpu, u64 tb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!cpumask_test_cpu(cpu, &wd_smp_cpus_pending)) {
 | 
				
			||||||
 | 
							if (unlikely(cpumask_test_cpu(cpu, &wd_smp_cpus_stuck))) {
 | 
				
			||||||
 | 
								unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pr_emerg("Watchdog CPU:%d became unstuck\n", cpu);
 | 
				
			||||||
 | 
								wd_smp_lock(&flags);
 | 
				
			||||||
 | 
								cpumask_clear_cpu(cpu, &wd_smp_cpus_stuck);
 | 
				
			||||||
 | 
								wd_smp_unlock(&flags);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cpumask_clear_cpu(cpu, &wd_smp_cpus_pending);
 | 
				
			||||||
 | 
						if (cpumask_empty(&wd_smp_cpus_pending)) {
 | 
				
			||||||
 | 
							unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wd_smp_lock(&flags);
 | 
				
			||||||
 | 
							if (cpumask_empty(&wd_smp_cpus_pending)) {
 | 
				
			||||||
 | 
								wd_smp_last_reset_tb = tb;
 | 
				
			||||||
 | 
								cpumask_andnot(&wd_smp_cpus_pending,
 | 
				
			||||||
 | 
										&wd_cpus_enabled,
 | 
				
			||||||
 | 
										&wd_smp_cpus_stuck);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wd_smp_unlock(&flags);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void watchdog_timer_interrupt(int cpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u64 tb = get_tb();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						per_cpu(wd_timer_tb, cpu) = tb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wd_smp_clear_cpu_pending(cpu, tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((s64)(tb - wd_smp_last_reset_tb) >= (s64)wd_smp_panic_timeout_tb)
 | 
				
			||||||
 | 
							watchdog_smp_panic(cpu, tb);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void soft_nmi_interrupt(struct pt_regs *regs)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						int cpu = raw_smp_processor_id();
 | 
				
			||||||
 | 
						u64 tb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!cpumask_test_cpu(cpu, &wd_cpus_enabled))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nmi_enter();
 | 
				
			||||||
 | 
						tb = get_tb();
 | 
				
			||||||
 | 
						if (tb - per_cpu(wd_timer_tb, cpu) >= wd_panic_timeout_tb) {
 | 
				
			||||||
 | 
							per_cpu(wd_timer_tb, cpu) = tb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wd_smp_lock(&flags);
 | 
				
			||||||
 | 
							if (cpumask_test_cpu(cpu, &wd_smp_cpus_stuck)) {
 | 
				
			||||||
 | 
								wd_smp_unlock(&flags);
 | 
				
			||||||
 | 
								goto out;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							set_cpu_stuck(cpu, tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pr_emerg("Watchdog CPU:%d Hard LOCKUP\n", cpu);
 | 
				
			||||||
 | 
							print_modules();
 | 
				
			||||||
 | 
							print_irqtrace_events(current);
 | 
				
			||||||
 | 
							if (regs)
 | 
				
			||||||
 | 
								show_regs(regs);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								dump_stack();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wd_smp_unlock(&flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (sysctl_hardlockup_all_cpu_backtrace)
 | 
				
			||||||
 | 
								trigger_allbutself_cpu_backtrace();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (hardlockup_panic)
 | 
				
			||||||
 | 
								nmi_panic(regs, "Hard LOCKUP");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (wd_panic_timeout_tb < 0x7fffffff)
 | 
				
			||||||
 | 
							mtspr(SPRN_DEC, wd_panic_timeout_tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						nmi_exit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void wd_timer_reset(unsigned int cpu, struct timer_list *t)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						t->expires = jiffies + msecs_to_jiffies(wd_timer_period_ms);
 | 
				
			||||||
 | 
						if (wd_timer_period_ms > 1000)
 | 
				
			||||||
 | 
							t->expires = __round_jiffies_up(t->expires, cpu);
 | 
				
			||||||
 | 
						add_timer_on(t, cpu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void wd_timer_fn(unsigned long data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct timer_list *t = this_cpu_ptr(&wd_timer);
 | 
				
			||||||
 | 
						int cpu = smp_processor_id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watchdog_timer_interrupt(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wd_timer_reset(cpu, t);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void arch_touch_nmi_watchdog(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int cpu = smp_processor_id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watchdog_timer_interrupt(cpu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL(arch_touch_nmi_watchdog);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void start_watchdog_timer_on(unsigned int cpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct timer_list *t = per_cpu_ptr(&wd_timer, cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						per_cpu(wd_timer_tb, cpu) = get_tb();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setup_pinned_timer(t, wd_timer_fn, 0);
 | 
				
			||||||
 | 
						wd_timer_reset(cpu, t);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void stop_watchdog_timer_on(unsigned int cpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct timer_list *t = per_cpu_ptr(&wd_timer, cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						del_timer_sync(t);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int start_wd_on_cpu(unsigned int cpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (cpumask_test_cpu(cpu, &wd_cpus_enabled)) {
 | 
				
			||||||
 | 
							WARN_ON(1);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!(watchdog_enabled & NMI_WATCHDOG_ENABLED))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (watchdog_suspended)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!cpumask_test_cpu(cpu, &watchdog_cpumask))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cpumask_set_cpu(cpu, &wd_cpus_enabled);
 | 
				
			||||||
 | 
						if (cpumask_weight(&wd_cpus_enabled) == 1) {
 | 
				
			||||||
 | 
							cpumask_set_cpu(cpu, &wd_smp_cpus_pending);
 | 
				
			||||||
 | 
							wd_smp_last_reset_tb = get_tb();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						smp_wmb();
 | 
				
			||||||
 | 
						start_watchdog_timer_on(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int stop_wd_on_cpu(unsigned int cpu)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!cpumask_test_cpu(cpu, &wd_cpus_enabled))
 | 
				
			||||||
 | 
							return 0; /* Can happen in CPU unplug case */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stop_watchdog_timer_on(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cpumask_clear_cpu(cpu, &wd_cpus_enabled);
 | 
				
			||||||
 | 
						wd_smp_clear_cpu_pending(cpu, get_tb());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void watchdog_calc_timeouts(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						wd_panic_timeout_tb = watchdog_thresh * ppc_tb_freq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Have the SMP detector trigger a bit later */
 | 
				
			||||||
 | 
						wd_smp_panic_timeout_tb = wd_panic_timeout_tb * 3 / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* 2/5 is the factor that the perf based detector uses */
 | 
				
			||||||
 | 
						wd_timer_period_ms = watchdog_thresh * 1000 * 2 / 5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void watchdog_nmi_reconfigure(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int cpu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watchdog_calc_timeouts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for_each_cpu(cpu, &wd_cpus_enabled)
 | 
				
			||||||
 | 
							stop_wd_on_cpu(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for_each_cpu_and(cpu, cpu_online_mask, &watchdog_cpumask)
 | 
				
			||||||
 | 
							start_wd_on_cpu(cpu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * This runs after lockup_detector_init() which sets up watchdog_cpumask.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int __init powerpc_watchdog_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watchdog_calc_timeouts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powerpc/watchdog:online",
 | 
				
			||||||
 | 
									start_wd_on_cpu, stop_wd_on_cpu);
 | 
				
			||||||
 | 
						if (err < 0)
 | 
				
			||||||
 | 
							pr_warn("Watchdog could not be initialized");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					arch_initcall(powerpc_watchdog_init);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void handle_backtrace_ipi(struct pt_regs *regs)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						nmi_cpu_backtrace(regs);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void raise_backtrace_ipi(cpumask_t *mask)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int cpu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for_each_cpu(cpu, mask) {
 | 
				
			||||||
 | 
							if (cpu == smp_processor_id())
 | 
				
			||||||
 | 
								handle_backtrace_ipi(NULL);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								smp_send_nmi_ipi(cpu, handle_backtrace_ipi, 1000000);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void arch_trigger_cpumask_backtrace(const cpumask_t *mask, bool exclude_self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						nmi_trigger_cpumask_backtrace(mask, exclude_self, raise_backtrace_ipi);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in a new issue