
commit 1f5e7eb7868e42227ac426c96d437117e6e06e8e upstream.
Tony reported intermittent lockups on poweroff. His analysis identified the
wbinvd() in stop_this_cpu() as the culprit. This was added to ensure that
on SME enabled machines a kexec() does not leave any stale data in the
caches when switching from encrypted to non-encrypted mode or vice versa.
That wbinvd() is conditional on the SME feature bit which is read directly
from CPUID. But that readout does not check whether the CPUID leaf is
available or not. If it's not available the CPU will return the value of
the highest supported leaf instead. Depending on the content the "SME" bit
might be set or not.
That's incorrect but harmless. Making the CPUID readout conditional makes
the observed hangs go away, but it does not fix the underlying problem:
CPU0 CPU1
stop_other_cpus()
send_IPIs(REBOOT); stop_this_cpu()
while (num_online_cpus() > 1); set_online(false);
proceed... -> hang
wbinvd()
WBINVD is an expensive operation and if multiple CPUs issue it at the same
time the resulting delays are even larger.
But CPU0 already observed num_online_cpus() going down to 1 and proceeds
which causes the system to hang.
This issue exists independent of WBINVD, but the delays caused by WBINVD
make it more prominent.
Make this more robust by adding a cpumask which is initialized to the
online CPU mask before sending the IPIs and CPUs clear their bit in
stop_this_cpu() after the WBINVD completed. Check for that cpumask to
become empty in stop_other_cpus() instead of watching num_online_cpus().
The cpumask cannot plug all holes either, but it's better than a raw
counter and allows to restrict the NMI fallback IPI to be sent only the
CPUs which have not reported within the timeout window.
Fixes: 08f253ec37
("x86/cpu: Clear SME feature flag when not in use")
Reported-by: Tony Battersby <tonyb@cybernetics.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Ashok Raj <ashok.raj@intel.com>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/3817d810-e0f1-8ef8-0bbd-663b919ca49b@cybernetics.com
Link: https://lore.kernel.org/r/87h6r770bv.ffs@tglx
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
102 lines
2.3 KiB
C
102 lines
2.3 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _ASM_X86_CPU_H
|
|
#define _ASM_X86_CPU_H
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/topology.h>
|
|
#include <linux/nodemask.h>
|
|
#include <linux/percpu.h>
|
|
#include <asm/ibt.h>
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
extern void prefill_possible_map(void);
|
|
|
|
#else /* CONFIG_SMP */
|
|
|
|
static inline void prefill_possible_map(void) {}
|
|
|
|
#define cpu_physical_id(cpu) boot_cpu_physical_apicid
|
|
#define cpu_acpi_id(cpu) 0
|
|
#define safe_smp_processor_id() 0
|
|
|
|
#endif /* CONFIG_SMP */
|
|
|
|
struct x86_cpu {
|
|
struct cpu cpu;
|
|
};
|
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
extern int arch_register_cpu(int num);
|
|
extern void arch_unregister_cpu(int);
|
|
extern void start_cpu0(void);
|
|
#ifdef CONFIG_DEBUG_HOTPLUG_CPU0
|
|
extern int _debug_hotplug_cpu(int cpu, int action);
|
|
#endif
|
|
#endif
|
|
|
|
extern void ap_init_aperfmperf(void);
|
|
|
|
int mwait_usable(const struct cpuinfo_x86 *);
|
|
|
|
unsigned int x86_family(unsigned int sig);
|
|
unsigned int x86_model(unsigned int sig);
|
|
unsigned int x86_stepping(unsigned int sig);
|
|
#ifdef CONFIG_CPU_SUP_INTEL
|
|
extern void __init sld_setup(struct cpuinfo_x86 *c);
|
|
extern bool handle_user_split_lock(struct pt_regs *regs, long error_code);
|
|
extern bool handle_guest_split_lock(unsigned long ip);
|
|
extern void handle_bus_lock(struct pt_regs *regs);
|
|
u8 get_this_hybrid_cpu_type(void);
|
|
#else
|
|
static inline void __init sld_setup(struct cpuinfo_x86 *c) {}
|
|
static inline bool handle_user_split_lock(struct pt_regs *regs, long error_code)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline bool handle_guest_split_lock(unsigned long ip)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline void handle_bus_lock(struct pt_regs *regs) {}
|
|
|
|
static inline u8 get_this_hybrid_cpu_type(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_IA32_FEAT_CTL
|
|
void init_ia32_feat_ctl(struct cpuinfo_x86 *c);
|
|
#else
|
|
static inline void init_ia32_feat_ctl(struct cpuinfo_x86 *c) {}
|
|
#endif
|
|
|
|
extern __noendbr void cet_disable(void);
|
|
|
|
struct ucode_cpu_info;
|
|
|
|
int intel_cpu_collect_info(struct ucode_cpu_info *uci);
|
|
|
|
static inline bool intel_cpu_signatures_match(unsigned int s1, unsigned int p1,
|
|
unsigned int s2, unsigned int p2)
|
|
{
|
|
if (s1 != s2)
|
|
return false;
|
|
|
|
/* Processor flags are either both 0 ... */
|
|
if (!p1 && !p2)
|
|
return true;
|
|
|
|
/* ... or they intersect. */
|
|
return p1 & p2;
|
|
}
|
|
|
|
extern u64 x86_read_arch_cap_msr(void);
|
|
|
|
extern struct cpumask cpus_stop_mask;
|
|
|
|
#endif /* _ASM_X86_CPU_H */
|