arm64: Handle early CPU boot failures
authorSuzuki K Poulose <suzuki.poulose@arm.com>
Tue, 23 Feb 2016 10:31:42 +0000 (10:31 +0000)
committerCatalin Marinas <catalin.marinas@arm.com>
Thu, 25 Feb 2016 10:32:23 +0000 (10:32 +0000)
A secondary CPU could fail to come online due to insufficient
capabilities and could simply die or loop in the kernel.
e.g, a CPU with no support for the selected kernel PAGE_SIZE
loops in kernel with MMU turned off.
or a hotplugged CPU which doesn't have one of the advertised
system capability will die during the activation.

There is no way to synchronise the status of the failing CPU
back to the master. This patch solves the issue by adding a
field to the secondary_data which can be updated by the failing
CPU. If the secondary CPU fails even before turning the MMU on,
it updates the status in a special variable reserved in the head.txt
section to make sure that the update can be cache invalidated safely
without possible sharing of cache write back granule.

Here are the possible states :

 -1. CPU_MMU_OFF - Initial value set by the master CPU, this value
indicates that the CPU could not turn the MMU on, hence the status
could not be reliably updated in the secondary_data. Instead, the
CPU has updated the status @ __early_cpu_boot_status.

 0. CPU_BOOT_SUCCESS - CPU has booted successfully.

 1. CPU_KILL_ME - CPU has invoked cpu_ops->die, indicating the
master CPU to synchronise by issuing a cpu_ops->cpu_kill.

 2. CPU_STUCK_IN_KERNEL - CPU couldn't invoke die(), instead is
looping in the kernel. This information could be used by say,
kexec to check if it is really safe to do a kexec reboot.

 3. CPU_PANIC_KERNEL - CPU detected some serious issues which
requires kernel to crash immediately. The secondary CPU cannot
call panic() until it has initialised the GIC. This flag can
be used to instruct the master to do so.

Cc: Mark Rutland <mark.rutland@arm.com>
Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
[catalin.marinas@arm.com: conflict resolution]
[catalin.marinas@arm.com: converted "status" from int to long]
[catalin.marinas@arm.com: updated update_early_cpu_boot_status to use str_l]
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
arch/arm64/include/asm/smp.h
arch/arm64/kernel/asm-offsets.c
arch/arm64/kernel/head.S
arch/arm64/kernel/smp.c

index 51913be35a0a21814dac2d08d350a329aa233eb3..817a067ba05817632181dee6049a5b4e6964bd80 100644 (file)
 #ifndef __ASM_SMP_H
 #define __ASM_SMP_H
 
+/* Values for secondary_data.status */
+
+#define CPU_MMU_OFF            (-1)
+#define CPU_BOOT_SUCCESS       (0)
+/* The cpu invoked ops->cpu_die, synchronise it with cpu_kill */
+#define CPU_KILL_ME            (1)
+/* The cpu couldn't die gracefully and is looping in the kernel */
+#define CPU_STUCK_IN_KERNEL    (2)
+/* Fatal system error detected by secondary CPU, crash the system */
+#define CPU_PANIC_KERNEL       (3)
+
+#ifndef __ASSEMBLY__
+
 #include <linux/threads.h>
 #include <linux/cpumask.h>
 #include <linux/thread_info.h>
@@ -54,11 +67,17 @@ asmlinkage void secondary_start_kernel(void);
 
 /*
  * Initial data for bringing up a secondary CPU.
+ * @stack  - sp for the secondary CPU
+ * @status - Result passed back from the secondary CPU to
+ *           indicate failure.
  */
 struct secondary_data {
        void *stack;
+       long status;
 };
+
 extern struct secondary_data secondary_data;
+extern long __early_cpu_boot_status;
 extern void secondary_entry(void);
 
 extern void arch_send_call_function_single_ipi(int cpu);
@@ -87,4 +106,13 @@ static inline void cpu_park_loop(void)
        }
 }
 
+static inline void update_cpu_boot_status(int val)
+{
+       WRITE_ONCE(secondary_data.status, val);
+       /* Ensure the visibility of the status update */
+       dsb(ishst);
+}
+
+#endif /* ifndef __ASSEMBLY__ */
+
 #endif /* ifndef __ASM_SMP_H */
index fffa4ac6c25a31a8dc7b2d1865b6c2f459aa64f5..463fcf1aca8a76039166741a28ea95e1c4cc5aec 100644 (file)
@@ -104,6 +104,8 @@ int main(void)
   DEFINE(TZ_MINWEST,           offsetof(struct timezone, tz_minuteswest));
   DEFINE(TZ_DSTTIME,           offsetof(struct timezone, tz_dsttime));
   BLANK();
+  DEFINE(CPU_BOOT_STACK,       offsetof(struct secondary_data, stack));
+  BLANK();
 #ifdef CONFIG_KVM_ARM_HOST
   DEFINE(VCPU_CONTEXT,         offsetof(struct kvm_vcpu, arch.ctxt));
   DEFINE(CPU_GP_REGS,          offsetof(struct kvm_cpu_context, gp_regs));
index 319f896c6e744a3ee47f4fede17a75bee4d7d9e2..50c2134a4aaffc99989f4ab71a3c8fd86271ffa7 100644 (file)
@@ -35,6 +35,7 @@
 #include <asm/pgtable-hwdef.h>
 #include <asm/pgtable.h>
 #include <asm/page.h>
+#include <asm/smp.h>
 #include <asm/sysreg.h>
 #include <asm/thread_info.h>
 #include <asm/virt.h>
@@ -686,7 +687,8 @@ ENTRY(__secondary_switched)
        msr     vbar_el1, x5
        isb
 
-       ldr_l   x0, secondary_data              // get secondary_data.stack
+       adr_l   x0, secondary_data
+       ldr     x0, [x0, #CPU_BOOT_STACK]       // get secondary_data.stack
        mov     sp, x0
        and     x0, x0, #~(THREAD_SIZE - 1)
        msr     sp_el0, x0                      // save thread_info
@@ -694,6 +696,29 @@ ENTRY(__secondary_switched)
        b       secondary_start_kernel
 ENDPROC(__secondary_switched)
 
+/*
+ * The booting CPU updates the failed status @__early_cpu_boot_status,
+ * with MMU turned off.
+ *
+ * update_early_cpu_boot_status tmp, status
+ *  - Corrupts tmp1, tmp2
+ *  - Writes 'status' to __early_cpu_boot_status and makes sure
+ *    it is committed to memory.
+ */
+
+       .macro  update_early_cpu_boot_status status, tmp1, tmp2
+       mov     \tmp2, #\status
+       str_l   \tmp2, __early_cpu_boot_status, \tmp1
+       dmb     sy
+       dc      ivac, \tmp1                     // Invalidate potentially stale cache line
+       .endm
+
+       .pushsection    .data..cacheline_aligned
+       .align  L1_CACHE_SHIFT
+ENTRY(__early_cpu_boot_status)
+       .long   0
+       .popsection
+
 /*
  * Enable the MMU.
  *
@@ -712,6 +737,7 @@ __enable_mmu:
        ubfx    x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
        cmp     x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
        b.ne    __no_granule_support
+       update_early_cpu_boot_status 0, x1, x2
        msr     ttbr0_el1, x25                  // load TTBR0
        msr     ttbr1_el1, x26                  // load TTBR1
        isb
@@ -748,6 +774,10 @@ __enable_mmu:
 ENDPROC(__enable_mmu)
 
 __no_granule_support:
+       /* Indicate that this CPU can't boot and is stuck in the kernel */
+       update_early_cpu_boot_status CPU_STUCK_IN_KERNEL, x1, x2
+1:
        wfe
-       b __no_granule_support
+       wfi
+       b 1b
 ENDPROC(__no_granule_support)
index 8d09f597024da28bca5b47d618876f34f52b4508..d07b196543076cb5dbe295c002b950b596a8d10b 100644 (file)
@@ -63,6 +63,8 @@
  * where to place its SVC stack
  */
 struct secondary_data secondary_data;
+/* Number of CPUs which aren't online, but looping in kernel text. */
+int cpus_stuck_in_kernel;
 
 enum ipi_msg_type {
        IPI_RESCHEDULE,
@@ -73,6 +75,16 @@ enum ipi_msg_type {
        IPI_WAKEUP
 };
 
+#ifdef CONFIG_HOTPLUG_CPU
+static int op_cpu_kill(unsigned int cpu);
+#else
+static inline int op_cpu_kill(unsigned int cpu)
+{
+       return -ENOSYS;
+}
+#endif
+
+
 /*
  * Boot a secondary CPU, and assign it the specified idle task.
  * This also gives us the initial stack to use for this CPU.
@@ -90,12 +102,14 @@ static DECLARE_COMPLETION(cpu_running);
 int __cpu_up(unsigned int cpu, struct task_struct *idle)
 {
        int ret;
+       long status;
 
        /*
         * We need to tell the secondary core where to find its stack and the
         * page tables.
         */
        secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
+       update_cpu_boot_status(CPU_MMU_OFF);
        __flush_dcache_area(&secondary_data, sizeof(secondary_data));
 
        /*
@@ -119,6 +133,32 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
        }
 
        secondary_data.stack = NULL;
+       status = READ_ONCE(secondary_data.status);
+       if (ret && status) {
+
+               if (status == CPU_MMU_OFF)
+                       status = READ_ONCE(__early_cpu_boot_status);
+
+               switch (status) {
+               default:
+                       pr_err("CPU%u: failed in unknown state : 0x%lx\n",
+                                       cpu, status);
+                       break;
+               case CPU_KILL_ME:
+                       if (!op_cpu_kill(cpu)) {
+                               pr_crit("CPU%u: died during early boot\n", cpu);
+                               break;
+                       }
+                       /* Fall through */
+                       pr_crit("CPU%u: may not have shut down cleanly\n", cpu);
+               case CPU_STUCK_IN_KERNEL:
+                       pr_crit("CPU%u: is stuck in kernel\n", cpu);
+                       cpus_stuck_in_kernel++;
+                       break;
+               case CPU_PANIC_KERNEL:
+                       panic("CPU%u detected unsupported configuration\n", cpu);
+               }
+       }
 
        return ret;
 }
@@ -184,6 +224,9 @@ asmlinkage void secondary_start_kernel(void)
         */
        pr_info("CPU%u: Booted secondary processor [%08x]\n",
                                         cpu, read_cpuid_id());
+       update_cpu_boot_status(CPU_BOOT_SUCCESS);
+       /* Make sure the status update is visible before we complete */
+       smp_wmb();
        set_cpu_online(cpu, true);
        complete(&cpu_running);
 
@@ -326,10 +369,12 @@ void cpu_die_early(void)
        set_cpu_present(cpu, 0);
 
 #ifdef CONFIG_HOTPLUG_CPU
+       update_cpu_boot_status(CPU_KILL_ME);
        /* Check if we can park ourselves */
        if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_die)
                cpu_ops[cpu]->cpu_die(cpu);
 #endif
+       update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
 
        cpu_park_loop();
 }