mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +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 | ||||
| 	bool | ||||
| 	depends on SMP && (DEBUGGER || KEXEC_CORE) | ||||
| 	depends on SMP && (DEBUGGER || KEXEC_CORE || HARDLOCKUP_DETECTOR) | ||||
| 	default y | ||||
| 
 | ||||
| config STACKTRACE_SUPPORT | ||||
|  | @ -192,12 +192,13 @@ config PPC | |||
| 	select HAVE_MEMBLOCK | ||||
| 	select HAVE_MEMBLOCK_NODE_MAP | ||||
| 	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_OPTPROBES			if PPC64 | ||||
| 	select HAVE_PERF_EVENTS | ||||
| 	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_USER_STACK_DUMP | ||||
| 	select HAVE_RCU_TABLE_FREE		if SMP | ||||
|  |  | |||
|  | @ -1,4 +1,15 @@ | |||
| #ifndef _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 */ | ||||
|  |  | |||
|  | @ -55,6 +55,8 @@ struct smp_ops_t { | |||
| 	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 start_secondary_resume(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 \
 | ||||
| 				   paca.o nvram_64.o firmware.o | ||||
| obj-$(CONFIG_VDSO32)		+= vdso32/ | ||||
| obj-$(CONFIG_HARDLOCKUP_DETECTOR)	+= watchdog.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_power.o | ||||
|  |  | |||
|  | @ -1314,6 +1314,31 @@ EXC_REAL_NONE(0x1800, 0x100) | |||
| EXC_VIRT_NONE(0x5800, 0x100) | ||||
| #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: | ||||
|  | @ -1336,7 +1361,7 @@ masked_##_H##interrupt:					\ | |||
| 	lis	r10,0x7fff;				\
 | ||||
| 	ori	r10,r10,0xffff;				\
 | ||||
| 	mtspr	SPRN_DEC,r10;				\
 | ||||
| 	b	2f;					\
 | ||||
| 	b	MASKED_DEC_HANDLER_LABEL;		\
 | ||||
| 1:	cmpwi	r10,PACA_IRQ_DBELL;			\
 | ||||
| 	beq	2f;					\
 | ||||
| 	cmpwi	r10,PACA_IRQ_HMI;			\
 | ||||
|  | @ -1351,7 +1376,8 @@ masked_##_H##interrupt:					\ | |||
| 	ld	r11,PACA_EXGEN+EX_R11(r13);		\
 | ||||
| 	GET_SCRATCH0(r13);				\
 | ||||
| 	##_H##rfid;					\ | ||||
| 	b	. | ||||
| 	b	.;					\
 | ||||
| 	MASKED_DEC_HANDLER(_H) | ||||
| 
 | ||||
| /* | ||||
|  * Real mode exceptions actually use this too, but alternate | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ | |||
| #include <linux/kvm_para.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/of.h> | ||||
| #include <linux/nmi.h> /* hardlockup_detector_disable() */ | ||||
| 
 | ||||
| #include <asm/reg.h> | ||||
| #include <asm/sections.h> | ||||
|  | @ -718,6 +719,12 @@ static __init void kvm_free_tmp(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()) | ||||
| 		goto free_tmp; | ||||
| 
 | ||||
|  |  | |||
|  | @ -751,22 +751,3 @@ unsigned long memory_block_size_bytes(void) | |||
| struct ppc_pci_io ppc_pci_io; | ||||
| EXPORT_SYMBOL(ppc_pci_io); | ||||
| #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. | ||||
|  * - fn is the target callback function. | ||||
|  * - delay_us > 0 is the delay before giving up waiting for targets to | ||||
|  *   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; | ||||
| 	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
	
	 Nicholas Piggin
						Nicholas Piggin