From: Xiongfeng Wang <wangxiongfeng2@huawei.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/9856 -------------------------------- Commit 08ae95f4fd3b ("nohz_full: Allow the boot CPU to be nohz_full") allows the boot CPU to be a nohz_full CPU, handing over its housekeeping duty. But it is only supported when CONFIG_PM_SLEEP_SMP is not configured, or when it is configured and the arch allows suspend on non-zero CPUs. We cannot disable CONFIG_PM_SLEEP_SMP because freeze_secondary_cpus(), which is used in kexec, is included in the config. For arm64, there is no restriction in kernel to prevent nonzero cpu entering suspend using PSCI interface. when ARCH_SUSPEND_NONZERO_CPU is enabled, PM_SLEEP_SMP_NONZERO_CPU will be also enabled. The only influence is that suspend_disable_secondary_cpus() will use a housekeeping CPU to enter suspend. If pm sleep/hibernate is not involved, only kexec will use this function, which is tested on an arm64 server, and the vmcore can be created successfully. We add 'support_cpu0_nohz_full' in the CONFIG_PM_SLEEP_SMP_NONZERO_CPU check branch. When 'support_cpu0_nohz_full' is zero. There is no functional change. Besides, the normal kexec path (kexec -e, not the crash path) calls machine_shutdown() -> smp_shutdown_nonboot_cpus(reboot_cpu), with reboot_cpu being 0. After this patch makes 'nohz_full=' masks including CPU0 possible, tick_do_timer_cpu is handed over to a non-zero housekeeping CPU, and the offline of that CPU is rejected by tick_nohz_cpu_down() with -EBUSY. smp_shutdown_nonboot_cpus() then hits its unconditional BUG_ON(num_online_cpus() > 1) and kexec fails. The vmcore test above does not cover this path (the crash path goes through machine_crash_shutdown() instead). Fix it by extending smp_shutdown_nonboot_cpus() to accept primary_cpu == -1, which selects the CPU owning the do_timer duty (exposed by the new tick_get_do_timer_cpu() helper) as the reboot CPU, falling back to the first online CPU when the duty holder is a sentinel value or offline, and make arm64's machine_shutdown() pass -1 when 'support_cpu0_nohz_full' is given and CPU0 is in the nohz_full mask. Anchoring the reboot CPU to the do_timer owner matters on multi-NUMA systems: the alternative of picking a housekeeping TIMER CPU via housekeeping_any_cpu() would return the housekeeping CPU closest to the current node, while the reboot task itself may have been migrated by set_cpus_allowed_ptr() to a ~random CPU of its allowed mask. The selected housekeeping CPU then may differ from the do_timer owner, whose offline is still rejected by tick_nohz_cpu_down(), and the BUG_ON(num_online_cpus() > 1) is hit again. Since the reboot task was pinned to reboot_cpu (CPU0) by migrate_to_reboot_cpu() and CPU0 is now offlined by the shutdown loop, also move it to the do_timer owner (or, on a sentinel value, to the housekeeping TIMER cpumask) before calling smp_shutdown_nonboot_cpus(), so the task which executes machine_kexec() ends up on the surviving reboot CPU, as it did before. Without the boot parameter, reboot_cpu keeps being used and the behavior is unchanged. Signed-off-by: Xiongfeng Wang <wangxiongfeng2@huawei.com> Signed-off-by: Xia Fukun <xiafukun@huawei.com> --- arch/arm64/Kconfig | 3 +++ arch/arm64/kernel/process.c | 27 ++++++++++++++++++++++++++- include/linux/cpu.h | 5 ++++- include/linux/tick.h | 2 ++ kernel/cpu.c | 14 ++++++++++++++ kernel/time/tick-common.c | 9 +++++++++ kernel/time/tick-sched.c | 2 +- 7 files changed, 59 insertions(+), 3 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 182a6a06b299..2f6f5931f718 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -2870,6 +2870,9 @@ config ARCH_HIBERNATION_HEADER config ARCH_SUSPEND_POSSIBLE def_bool y +config ARCH_SUSPEND_NONZERO_CPU + def_bool y + config ARCH_CPUIDLE_HALTPOLL bool "Enable selection of the cpuidle-haltpoll driver" help diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index e8e0e11b1c4d..0807a181415e 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -14,6 +14,9 @@ #include <linux/sched/debug.h> #include <linux/sched/task.h> #include <linux/sched/task_stack.h> +#ifndef __GENKSYMS__ +#include <linux/sched/isolation.h> +#endif #include <linux/kernel.h> #include <linux/mman.h> #include <linux/mm.h> @@ -87,7 +90,29 @@ void __noreturn arch_cpu_idle_dead(void) */ void machine_shutdown(void) { - smp_shutdown_nonboot_cpus(reboot_cpu); + unsigned int primary_cpu = reboot_cpu; + + if (support_cpu0_nohz_full && tick_nohz_full_cpu(0)) { + unsigned int tick_cpu = tick_get_do_timer_cpu(); + + primary_cpu = -1; + /* + * The reboot task was pinned to reboot_cpu (CPU0) by + * migrate_to_reboot_cpu(), but CPU0 will be offlined by + * smp_shutdown_nonboot_cpus() below. Move it to the CPU + * which owns the do_timer duty (that CPU survives the + * offline loop), so the task which executes + * machine_kexec() stays on the reboot CPU, as it did + * before. + */ + if (tick_cpu >= 0 && tick_cpu < nr_cpu_ids) + set_cpus_allowed_ptr(current, cpumask_of(tick_cpu)); + else + set_cpus_allowed_ptr(current, + housekeeping_cpumask(HK_TYPE_TIMER)); + } + + smp_shutdown_nonboot_cpus(primary_cpu); } /* diff --git a/include/linux/cpu.h b/include/linux/cpu.h index 98e06cf4b49d..e7ec449d9134 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h @@ -20,6 +20,9 @@ #include <linux/cpuhotplug.h> #include <linux/cpuhplock.h> #include <linux/cpu_smt.h> +#ifndef __GENKSYMS__ +#include <linux/tick.h> +#endif struct device; struct device_node; @@ -143,7 +146,7 @@ static inline int suspend_disable_secondary_cpus(void) { int cpu = 0; - if (IS_ENABLED(CONFIG_PM_SLEEP_SMP_NONZERO_CPU)) + if (support_cpu0_nohz_full && IS_ENABLED(CONFIG_PM_SLEEP_SMP_NONZERO_CPU)) cpu = -1; return freeze_secondary_cpus(cpu); diff --git a/include/linux/tick.h b/include/linux/tick.h index 5c929c429007..2d2ee1956b84 100644 --- a/include/linux/tick.h +++ b/include/linux/tick.h @@ -21,12 +21,14 @@ extern void tick_suspend_local(void); extern void tick_resume_local(void); extern void tick_handover_do_timer(void); extern void tick_cleanup_dead_cpu(int cpu); +extern int tick_get_do_timer_cpu(void); #else /* CONFIG_GENERIC_CLOCKEVENTS */ static inline void tick_init(void) { } static inline void tick_suspend_local(void) { } static inline void tick_resume_local(void) { } static inline void tick_handover_do_timer(void) { } static inline void tick_cleanup_dead_cpu(int cpu) { } +static inline int tick_get_do_timer_cpu(void) { return -1; } #endif /* !CONFIG_GENERIC_CLOCKEVENTS */ #if defined(CONFIG_GENERIC_CLOCKEVENTS) && defined(CONFIG_SUSPEND) diff --git a/kernel/cpu.c b/kernel/cpu.c index 8a0955256578..4795b0929262 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c @@ -1530,6 +1530,20 @@ void smp_shutdown_nonboot_cpus(unsigned int primary_cpu) cpu_maps_update_begin(); + if (primary_cpu == -1) { + /* + * Prefer the CPU which owns the do_timer duty: the offline + * of that CPU is rejected by tick_nohz_cpu_down() when + * nohz_full is running, so picking it as the reboot CPU + * keeps the offline loop below away from that rejection. + */ + primary_cpu = tick_get_do_timer_cpu(); + if (primary_cpu < 0 || !cpu_online(primary_cpu)) + primary_cpu = cpumask_first(cpu_online_mask); + if (!housekeeping_cpu(primary_cpu, HK_TYPE_TIMER)) + primary_cpu = housekeeping_any_cpu(HK_TYPE_TIMER); + } + /* * Make certain the cpu I'm about to reboot on is online. * diff --git a/kernel/time/tick-common.c b/kernel/time/tick-common.c index ecdb8c2b2cab..6d47cc03a54d 100644 --- a/kernel/time/tick-common.c +++ b/kernel/time/tick-common.c @@ -396,6 +396,15 @@ void tick_handover_do_timer(void) tick_do_timer_cpu = cpumask_first(cpu_online_mask); } +/* + * Return the CPU which currently owns the do_timer duty. The value + * is either an online CPU or one of the TICK_DO_TIMER_* sentinels. + */ +int tick_get_do_timer_cpu(void) +{ + return READ_ONCE(tick_do_timer_cpu); +} + /* * Shutdown an event device on a given cpu: * diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 33a471692def..549160ecfd88 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -582,7 +582,7 @@ void __init tick_nohz_init(void) } if (IS_ENABLED(CONFIG_PM_SLEEP_SMP) && - !IS_ENABLED(CONFIG_PM_SLEEP_SMP_NONZERO_CPU)) { + (!support_cpu0_nohz_full || !IS_ENABLED(CONFIG_PM_SLEEP_SMP_NONZERO_CPU))) { cpu = smp_processor_id(); if (cpumask_test_cpu(cpu, tick_nohz_full_mask)) { -- 2.34.1