From: Xiongfeng Wang <wangxiongfeng2(a)huawei.com>
commit cfd4b6e7bd54244a014846f9f9cb2f460de50894 openEuler-1.0.
Add nmi_watchdog support for arm64 based on SDEI.
Signed-off-by: Xiongfeng Wang <wangxiongfeng2(a)huawei.com>
Reviewed-by: Kefeng Wang <wangkefeng.wang(a)huawei.com>
Signed-off-by: Yang Yingliang <yangyingliang(a)huawei.com>
Signed-off-by: Xin Hao <haoxing990(a)gmail.com>
---
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 6ec54c517c62..973cc777e7e4 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -57,6 +57,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 <asm/irq_regs.h>
+#include <asm/kvm_hyp.h>
+#include <asm/smp_plat.h>
+#include <asm/sdei.h>
+#include <asm/virt.h>
+#include <linux/arm_sdei.h>
+#include <linux/nmi.h>
+
+/* 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 46a910acce3f..8c26cc075681 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.
--
2.31.0