From cfd4b6e7bd54244a014846f9f9cb2f460de50894 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Mon, 28 Jan 2019 15:36:50 +0800 Subject: [PATCH] watchdog: add nmi_watchdog support for arm64 based on SDEI euler inclusion category: feature Bugzilla: 5515 CVE: N/A ---------------------------------------- Add nmi_watchdog support for arm64 based on SDEI. Signed-off-by: Xiongfeng Wang Reviewed-by: Kefeng Wang Signed-off-by: Yang Yingliang --- arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/watchdog_sdei.c | 134 ++++++++++++++++++++++++++++++ lib/Kconfig.debug | 6 ++ 3 files changed, 141 insertions(+) create mode 100644 arch/arm64/kernel/watchdog_sdei.c diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 74369475cde7..196f06b3a45d 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -59,6 +59,7 @@ arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_CRASH_CORE) += crash_core.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o arm64-obj-$(CONFIG_ARM64_SSBD) += ssbd.o +arm64-obj-$(CONFIG_SDEI_WATCHDOG) += watchdog_sdei.o obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c new file mode 100644 index 000000000000..fab1c575803f --- /dev/null +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Detect hard lockups on a system + * + * Note: Most of this code is borrowed heavily from the perf hardlockup + * detector, so thanks to Don for the initial implementation. + */ + +#define pr_fmt(fmt) "SDEI NMI watchdog: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* We use the arch virt timer as SDEI NMI watchdog timer */ +#define SDEI_NMI_WATCHDOG_HWIRQ 27 +#define SDEI_TIMER_INTERVAL 3 + +static int sdei_watchdog_event_num; +static bool disable_sdei_nmi_watchdog; + +static void start_arch_virt_timer(int seconds) +{ + int timer_freq = arch_timer_get_rate(); + + write_sysreg_el0(1, cntv_ctl); + write_sysreg_el0(timer_freq * seconds, cntv_tval); +} + +static void stop_arch_virt_timer(void) +{ + write_sysreg_el0(0, cntv_ctl); +} + +int watchdog_nmi_enable(unsigned int cpu) +{ + int ret; + + ret = sdei_api_event_enable(sdei_watchdog_event_num); + if (ret) { + pr_err("Enable NMI Watchdog failed on cpu%d\n", + smp_processor_id()); + return ret; + } + + start_arch_virt_timer(SDEI_TIMER_INTERVAL); + + return 0; +} + +void watchdog_nmi_disable(unsigned int cpu) +{ + int ret; + + ret = sdei_api_event_disable(sdei_watchdog_event_num); + if (ret) + pr_err("Disable NMI Watchdog failed on cpu%d\n", + smp_processor_id()); + + stop_arch_virt_timer(); +} + +static int sdei_watchdog_callback(u32 event, + struct pt_regs *regs, void *arg) +{ + /* reprogram the arch virt timer */ + start_arch_virt_timer(SDEI_TIMER_INTERVAL); + watchdog_hardlockup_check(regs); + + return 0; +} + +static void sdei_nmi_watchdog_bind(void *data) +{ + int ret; + + ret = sdei_api_event_interrupt_bind(SDEI_NMI_WATCHDOG_HWIRQ); + if (ret < 0) + pr_err("SDEI bind failed on cpu%d, return %d\n", + smp_processor_id(), ret); +} + +/* Before BIOS implements SDEI platform event, the host OS will use arch + * virt timer as SDEI watchdog timer, so the guest os will failed to start + * because it can not use arch virt timer. We provide a mechanism to disable + * SDEI NMI watchdog in the host. + */ +static int __init disable_sdei_nmi_watchdog_setup(char *str) +{ + disable_sdei_nmi_watchdog = true; + return 1; +} +__setup("disable_sdei_nmi_watchdog", disable_sdei_nmi_watchdog_setup); + +int __init watchdog_nmi_probe(void) +{ + int ret; + + if (disable_sdei_nmi_watchdog) + return -EINVAL; + + /* + * When hyp mode is not available and kernel is not in hyp mode, the system + * will use arch virt timer, which will conflict with SDEI NMI Watchdog. + * Refer to 'arch_timer_select_ppi'. + */ + if (!is_kernel_in_hyp_mode() && !is_hyp_mode_available()) { + pr_err("Disable SDEI NMI Watchdog because the system will use virt timer\n"); + return -EINVAL; + } + + sdei_watchdog_event_num = sdei_api_event_interrupt_bind(SDEI_NMI_WATCHDOG_HWIRQ); + if (sdei_watchdog_event_num < 0) { + pr_err("bind interrupt failed !\n"); + return sdei_watchdog_event_num; + } + + on_each_cpu(sdei_nmi_watchdog_bind, NULL, true); + + ret = sdei_event_register(sdei_watchdog_event_num, + sdei_watchdog_callback, NULL); + if (ret) { + pr_err("SDEI Watchdog register callback failed\n"); + return ret; + } + + pr_info("SDEI Watchdog registered successfully\n"); + + return 0; +} diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index f75c285d935b..d36aa479c5b4 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -836,6 +836,12 @@ config HARDLOCKUP_DETECTOR_PERF bool select SOFTLOCKUP_DETECTOR +config SDEI_WATCHDOG + bool "SDEI NMI Watchdog support" + depends on ARM_SDE_INTERFACE + select HAVE_HARDLOCKUP_DETECTOR_ARCH + select HARDLOCKUP_CHECK_TIMESTAMP + # # Enables a timestamp based low pass filter to compensate for perf based # hard lockup detection which runs too fast due to turbo modes. -- GitLab