ARM: avoid Cortex-A9 livelock on tight dmb loops
authorRussell King <rmk+kernel@armlinux.org.uk>
Tue, 10 Apr 2018 10:35:36 +0000 (11:35 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 5 Apr 2019 20:29:13 +0000 (22:29 +0200)
[ Upstream commit 5388a5b82199facacd3d7ac0d05aca6e8f902fed ]

machine_crash_nonpanic_core() does this:

while (1)
cpu_relax();

because the kernel has crashed, and we have no known safe way to deal
with the CPU.  So, we place the CPU into an infinite loop which we
expect it to never exit - at least not until the system as a whole is
reset by some method.

In the absence of erratum 754327, this code assembles to:

b .

In other words, an infinite loop.  When erratum 754327 is enabled,
this becomes:

1: dmb
b 1b

It has been observed that on some systems (eg, OMAP4) where, if a
crash is triggered, the system tries to kexec into the panic kernel,
but fails after taking the secondary CPU down - placing it into one
of these loops.  This causes the system to livelock, and the most
noticable effect is the system stops after issuing:

Loading crashdump kernel...

to the system console.

The tested as working solution I came up with was to add wfe() to
these infinite loops thusly:

while (1) {
cpu_relax();
wfe();
}

which, without 754327 builds to:

1: wfe
b 1b

or with 754327 is enabled:

1: dmb
wfe
b 1b

Adding "wfe" does two things depending on the environment we're running
under:
- where we're running on bare metal, and the processor implements
  "wfe", it stops us spinning endlessly in a loop where we're never
  going to do any useful work.
- if we're running in a VM, it allows the CPU to be given back to the
  hypervisor and rescheduled for other purposes (maybe a different VM)
  rather than wasting CPU cycles inside a crashed VM.

However, in light of erratum 794072, Will Deacon wanted to see 10 nops
as well - which is reasonable to cover the case where we have erratum
754327 enabled _and_ we have a processor that doesn't implement the
wfe hint.

So, we now end up with:

1:      wfe
        b       1b

when erratum 754327 is disabled, or:

1:      dmb
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        wfe
        b       1b

when erratum 754327 is enabled.  We also get the dmb + 10 nop
sequence elsewhere in the kernel, in terminating loops.

This is reasonable - it means we get the workaround for erratum
794072 when erratum 754327 is enabled, but still relinquish the dead
processor - either by placing it in a lower power mode when wfe is
implemented as such or by returning it to the hypervisior, or in the
case where wfe is a no-op, we use the workaround specified in erratum
794072 to avoid the problem.

These as two entirely orthogonal problems - the 10 nops addresses
erratum 794072, and the wfe is an optimisation that makes the system
more efficient when crashed either in terms of power consumption or
by allowing the host/other VMs to make use of the CPU.

I don't see any reason not to use kexec() inside a VM - it has the
potential to provide automated recovery from a failure of the VMs
kernel with the opportunity for saving a crashdump of the failure.
A panic() with a reboot timeout won't do that, and reading the
libvirt documentation, setting on_reboot to "preserve" won't either
(the documentation states "The preserve action for an on_reboot event
is treated as a destroy".)  Surely it has to be a good thing to
avoiding having CPUs spinning inside a VM that is doing no useful
work.

Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: Sasha Levin <sashal@kernel.org>
arch/arm/include/asm/barrier.h
arch/arm/include/asm/processor.h
arch/arm/kernel/machine_kexec.c
arch/arm/kernel/smp.c
arch/arm/mach-omap2/prm_common.c

index 513e03d138ea8517c102ff7e103069ad686bc9d6..8331cb0d34615b6d5dee622f7c5ec6170493df2b 100644 (file)
@@ -10,6 +10,8 @@
 #define sev()  __asm__ __volatile__ ("sev" : : : "memory")
 #define wfe()  __asm__ __volatile__ ("wfe" : : : "memory")
 #define wfi()  __asm__ __volatile__ ("wfi" : : : "memory")
+#else
+#define wfe()  do { } while (0)
 #endif
 
 #if __LINUX_ARM_ARCH__ >= 7
index 8a1e8e995daec45278d088faa3ada7f1d6dbb957..08509183c7df1b7b1ab862d37b92ff4ef9844e91 100644 (file)
@@ -77,7 +77,11 @@ extern void release_thread(struct task_struct *);
 unsigned long get_wchan(struct task_struct *p);
 
 #if __LINUX_ARM_ARCH__ == 6 || defined(CONFIG_ARM_ERRATA_754327)
-#define cpu_relax()                    smp_mb()
+#define cpu_relax()                                            \
+       do {                                                    \
+               smp_mb();                                       \
+               __asm__ __volatile__("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;");      \
+       } while (0)
 #else
 #define cpu_relax()                    barrier()
 #endif
index b18c1ea56bed69b8dd6ffbda657315bdd94230c6..ef6b27fe1d2e1853773c62ce13996eed71403cde 100644 (file)
@@ -87,8 +87,11 @@ void machine_crash_nonpanic_core(void *unused)
 
        set_cpu_online(smp_processor_id(), false);
        atomic_dec(&waiting_for_crash_ipi);
-       while (1)
+
+       while (1) {
                cpu_relax();
+               wfe();
+       }
 }
 
 static void machine_kexec_mask_interrupts(void)
index bc83ec7ed53fde602c006f8ba178f8f15b4f2940..7a5dc011c5230088f1b57ee6af35f1617fa2ca3a 100644 (file)
@@ -602,8 +602,10 @@ static void ipi_cpu_stop(unsigned int cpu)
        local_fiq_disable();
        local_irq_disable();
 
-       while (1)
+       while (1) {
                cpu_relax();
+               wfe();
+       }
 }
 
 static DEFINE_PER_CPU(struct completion *, cpu_completion);
index f1ca9479491bea2b88f2e8103e95b793428f9ac9..9e14604b9642645991a399f99b12a31c00a3c811 100644 (file)
@@ -533,8 +533,10 @@ void omap_prm_reset_system(void)
 
        prm_ll_data->reset_system();
 
-       while (1)
+       while (1) {
                cpu_relax();
+               wfe();
+       }
 }
 
 /**