Unverified Commit 0580e9e0 authored by openeuler-ci-bot's avatar openeuler-ci-bot Committed by Gitee
Browse files

!3211 Add SDEI Watchdog Support

Merge Pull Request from: @ci-robot 
 
PR sync from: Xiongfeng Wang <wangxiongfeng2@huawei.com>
https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/ASLSPTJDB6LV4KUHOI5P5OIWJ4L7BDZF/ 
Xiongfeng Wang (12):
  firmware: arm_sdei: add interrupt binding api
  firmware: arm_sdei: make 'sdei_api_event_disable/enable' public
  lockup_detector: init lockup detector after all the init_calls
  watchdog: add nmi_watchdog support for arm64 based on SDEI
  sdei_watchdog: clear EOI of the secure timer before kdump
  sdei_watchdog: set secure timer period base on 'watchdog_thresh'
  sdei_watchdog: avoid possible false hardlockup
  init: only move down lockup_detector_init() when sdei_watchdog is
    enabled
  kprobes/arm64: Blacklist sdei watchdog callback functions
  openeuler_defconfig: Enable SDEI Watchdog
  stop_machine: mask sdei before running the callback
  arm64: kexec: only clear EOI for SDEI in NMI context


-- 
2.20.1
 
https://gitee.com/openeuler/kernel/issues/I8LQCC 
 
Link:https://gitee.com/openeuler/kernel/pulls/3211

 

Reviewed-by: default avatarWei Li <liwei391@huawei.com>
Signed-off-by: default avatarZheng Zengkai <zhengzengkai@huawei.com>
parents 30d72ee3 2dfa9f6a
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -7747,11 +7747,12 @@ CONFIG_SOFTLOCKUP_DETECTOR=y
CONFIG_HAVE_HARDLOCKUP_DETECTOR_BUDDY=y
CONFIG_HARDLOCKUP_DETECTOR=y
# CONFIG_HARDLOCKUP_DETECTOR_PREFER_BUDDY is not set
CONFIG_HARDLOCKUP_DETECTOR_PERF=y
# CONFIG_HARDLOCKUP_DETECTOR_PERF is not set
# CONFIG_HARDLOCKUP_DETECTOR_BUDDY is not set
# CONFIG_HARDLOCKUP_DETECTOR_ARCH is not set
CONFIG_HARDLOCKUP_DETECTOR_COUNTS_HRTIMER=y
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
CONFIG_SDEI_WATCHDOG=y
CONFIG_DETECT_HUNG_TASK=y
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120
# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set
+1 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
obj-$(CONFIG_CRASH_DUMP)		+= crash_dump.o
obj-$(CONFIG_CRASH_CORE)		+= crash_core.o
obj-$(CONFIG_ARM_SDE_INTERFACE)		+= sdei.o
obj-$(CONFIG_SDEI_WATCHDOG)		+= watchdog_sdei.o
obj-$(CONFIG_ARM64_PTR_AUTH)		+= pointer_auth.o
obj-$(CONFIG_ARM64_MTE)			+= mte.o
obj-y					+= vdso-wrap.o
+11 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/kexec.h>
#include <linux/nmi.h>
#include <linux/page-flags.h>
#include <linux/reboot.h>
#include <linux/set_memory.h>
@@ -262,6 +263,16 @@ void machine_crash_shutdown(struct pt_regs *regs)
	/* shutdown non-crashing cpus */
	crash_smp_send_stop();

	/*
	 * when we panic in hardlockup detected by sdei_watchdog, the secure
	 * timer interrupt remains activate here because firmware clear eoi
	 * after dispatch is completed. This will cause arm_arch_timer
	 * interrupt failed to trigger in the second kernel. So we clear eoi
	 * of the secure timer before booting the second kernel.
	 */
	if (in_nmi())
		sdei_watchdog_clear_eoi();

	/* for crashing cpu */
	crash_save_cpu(regs, smp_processor_id());
	machine_kexec_mask_interrupts();
+151 −0
Original line number Diff line number Diff line
// 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/kprobes.h>
#include <linux/nmi.h>

/* We use the secure physical timer as SDEI NMI watchdog timer */
#define SDEI_NMI_WATCHDOG_HWIRQ		29

static int sdei_watchdog_event_num;
bool disable_sdei_nmi_watchdog;
static bool sdei_watchdog_registered;
static DEFINE_PER_CPU(ktime_t, last_check_time);

void watchdog_hardlockup_enable(unsigned int cpu)
{
	int ret;

	if (!sdei_watchdog_registered)
		return;

	/* Skip the first hardlockup check incase BIOS didn't init the
	 * secure timer correctly */
	watchdog_hardlockup_touch_cpu(cpu);
	sdei_api_set_secure_timer_period(watchdog_thresh);
	__this_cpu_write(last_check_time, ktime_get_mono_fast_ns());

	ret = sdei_api_event_enable(sdei_watchdog_event_num);
	if (ret) {
		pr_err("Enable NMI Watchdog failed on cpu%d\n",
				smp_processor_id());
	}
}

void watchdog_hardlockup_disable(unsigned int cpu)
{
	int ret;

	if (!sdei_watchdog_registered)
		return;

	ret = sdei_api_event_disable(sdei_watchdog_event_num);
	if (ret)
		pr_err("Disable NMI Watchdog failed on cpu%d\n",
				smp_processor_id());
}

static int sdei_watchdog_callback(u32 event,
		struct pt_regs *regs, void *arg)
{
	ktime_t delta, now = ktime_get_mono_fast_ns();

	delta = now - __this_cpu_read(last_check_time);
	__this_cpu_write(last_check_time, now);

	/*
	 * Set delta to 4/5 of the actual watchdog threshold period so the
	 * hrtimer is guaranteed to fire at least once within the real
	 * watchdog threshold.
	 */
	if (delta < watchdog_thresh * (u64)NSEC_PER_SEC * 4 / 5) {
		pr_err(FW_BUG "SDEI Watchdog event triggered too soon, "
			"time to last check:%lld ns\n", delta);
		return 0;
	}

	watchdog_hardlockup_check(smp_processor_id(), regs);

	return 0;
}
NOKPROBE_SYMBOL(sdei_watchdog_callback);

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);
}

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);

void sdei_watchdog_clear_eoi(void)
{
	if (sdei_watchdog_registered)
		sdei_api_clear_eoi(SDEI_NMI_WATCHDOG_HWIRQ);
}

int __init watchdog_hardlockup_probe(void)
{
	int ret;

	if (disable_sdei_nmi_watchdog)
		return -EINVAL;

	if (!is_hyp_mode_available()) {
		pr_err("Disable SDEI NMI Watchdog in VM\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. Firmware may not support SDEI !\n");
		return sdei_watchdog_event_num;
	}

	/*
	 * After we introduced 'sdei_api_set_secure_timer_period', we disselect
	 * 'CONFIG_HARDLOCKUP_CHECK_TIMESTAMP'. So we need to make sure that
	 * firmware can set the period of the secure timer and the timer
	 * interrupt doesn't trigger too soon.
	 */
	if (sdei_api_set_secure_timer_period(watchdog_thresh)) {
		pr_err("Firmware doesn't support setting the secure timer period, please update your BIOS !\n");
		return -EINVAL;
	}

	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;
	}

	sdei_watchdog_registered = true;
	pr_info("SDEI Watchdog registered successfully\n");

	return 0;
}
+24 −2
Original line number Diff line number Diff line
@@ -188,6 +188,28 @@ int sdei_api_event_context(u32 query, u64 *result)
}
NOKPROBE_SYMBOL(sdei_api_event_context);

int sdei_api_event_interrupt_bind(int hwirq)
{
	u64 event_number;

	invoke_sdei_fn(SDEI_1_0_FN_SDEI_INTERRUPT_BIND, hwirq, 0, 0, 0, 0,
			&event_number);

	return (int)event_number;
}

int sdei_api_clear_eoi(int hwirq)
{
	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_CLEAR_EOI, hwirq, 0, 0, 0, 0,
			NULL);
}

int sdei_api_set_secure_timer_period(int sec)
{
	return invoke_sdei_fn(SDEI_1_0_FN_SET_SECURE_TIMER_PERIOD, sec, 0, 0, 0,
			0, NULL);
}

static int sdei_api_event_get_info(u32 event, u32 info, u64 *result)
{
	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0,
@@ -379,7 +401,7 @@ static int sdei_platform_reset(void)
	return err;
}

static int sdei_api_event_enable(u32 event_num)
int sdei_api_event_enable(u32 event_num)
{
	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0,
			      0, NULL);
@@ -426,7 +448,7 @@ int sdei_event_enable(u32 event_num)
	return err;
}

static int sdei_api_event_disable(u32 event_num)
int sdei_api_event_disable(u32 event_num)
{
	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0,
			      0, 0, NULL);
Loading