Skip to content

Commit 4a9d7e3

Browse files
Suzuki K PouloseAKASHI Takahiro
authored andcommitted
arm64: Handle early CPU boot failures
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>
1 parent 7073377 commit 4a9d7e3

4 files changed

Lines changed: 107 additions & 2 deletions

File tree

arch/arm64/include/asm/smp.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616
#ifndef __ASM_SMP_H
1717
#define __ASM_SMP_H
1818

19+
/* Values for secondary_data.status */
20+
21+
#define CPU_MMU_OFF (-1)
22+
#define CPU_BOOT_SUCCESS (0)
23+
/* The cpu invoked ops->cpu_die, synchronise it with cpu_kill */
24+
#define CPU_KILL_ME (1)
25+
/* The cpu couldn't die gracefully and is looping in the kernel */
26+
#define CPU_STUCK_IN_KERNEL (2)
27+
/* Fatal system error detected by secondary CPU, crash the system */
28+
#define CPU_PANIC_KERNEL (3)
29+
30+
#ifndef __ASSEMBLY__
31+
1932
#include <linux/threads.h>
2033
#include <linux/cpumask.h>
2134
#include <linux/thread_info.h>
@@ -54,11 +67,17 @@ asmlinkage void secondary_start_kernel(void);
5467

5568
/*
5669
* Initial data for bringing up a secondary CPU.
70+
* @stack - sp for the secondary CPU
71+
* @status - Result passed back from the secondary CPU to
72+
* indicate failure.
5773
*/
5874
struct secondary_data {
5975
void *stack;
76+
long status;
6077
};
78+
6179
extern struct secondary_data secondary_data;
80+
extern long __early_cpu_boot_status;
6281
extern void secondary_entry(void);
6382

6483
extern void arch_send_call_function_single_ipi(int cpu);
@@ -87,4 +106,13 @@ static inline void cpu_park_loop(void)
87106
}
88107
}
89108

109+
static inline void update_cpu_boot_status(int val)
110+
{
111+
WRITE_ONCE(secondary_data.status, val);
112+
/* Ensure the visibility of the status update */
113+
dsb(ishst);
114+
}
115+
116+
#endif /* ifndef __ASSEMBLY__ */
117+
90118
#endif /* ifndef __ASM_SMP_H */

arch/arm64/kernel/asm-offsets.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ int main(void)
117117
DEFINE(TZ_MINWEST, offsetof(struct timezone, tz_minuteswest));
118118
DEFINE(TZ_DSTTIME, offsetof(struct timezone, tz_dsttime));
119119
BLANK();
120+
DEFINE(CPU_BOOT_STACK, offsetof(struct secondary_data, stack));
121+
BLANK();
120122
#ifdef CONFIG_KVM_ARM_HOST
121123
DEFINE(VCPU_CONTEXT, offsetof(struct kvm_vcpu, arch.ctxt));
122124
DEFINE(CPU_GP_REGS, offsetof(struct kvm_cpu_context, gp_regs));

arch/arm64/kernel/head.S

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <asm/pgtable-hwdef.h>
3737
#include <asm/pgtable.h>
3838
#include <asm/page.h>
39+
#include <asm/smp.h>
3940
#include <asm/sysreg.h>
4041
#include <asm/thread_info.h>
4142
#include <asm/virt.h>
@@ -643,14 +644,38 @@ __secondary_switched:
643644
msr vbar_el1, x5
644645
isb
645646

646-
ldr_l x0, secondary_data // get secondary_data.stack
647+
adr_l x0, secondary_data
648+
ldr x0, [x0, #CPU_BOOT_STACK] // get secondary_data.stack
647649
mov sp, x0
648650
and x0, x0, #~(THREAD_SIZE - 1)
649651
msr sp_el0, x0 // save thread_info
650652
mov x29, #0
651653
b secondary_start_kernel
652654
ENDPROC(__secondary_switched)
653655

656+
/*
657+
* The booting CPU updates the failed status @__early_cpu_boot_status,
658+
* with MMU turned off.
659+
*
660+
* update_early_cpu_boot_status tmp, status
661+
* - Corrupts tmp1, tmp2
662+
* - Writes 'status' to __early_cpu_boot_status and makes sure
663+
* it is committed to memory.
664+
*/
665+
666+
.macro update_early_cpu_boot_status status, tmp1, tmp2
667+
mov \tmp2, #\status
668+
str_l \tmp2, __early_cpu_boot_status, \tmp1
669+
dmb sy
670+
dc ivac, \tmp1 // Invalidate potentially stale cache line
671+
.endm
672+
673+
.pushsection .data..cacheline_aligned
674+
.align L1_CACHE_SHIFT
675+
ENTRY(__early_cpu_boot_status)
676+
.long 0
677+
.popsection
678+
654679
/*
655680
* Enable the MMU.
656681
*
@@ -669,6 +694,7 @@ ENTRY(__enable_mmu)
669694
ubfx x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
670695
cmp x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
671696
b.ne __no_granule_support
697+
update_early_cpu_boot_status 0, x1, x2
672698
msr ttbr0_el1, x25 // load TTBR0
673699
msr ttbr1_el1, x26 // load TTBR1
674700
isb
@@ -708,8 +734,12 @@ ENTRY(__enable_mmu)
708734
ENDPROC(__enable_mmu)
709735

710736
__no_granule_support:
737+
/* Indicate that this CPU can't boot and is stuck in the kernel */
738+
update_early_cpu_boot_status CPU_STUCK_IN_KERNEL, x1, x2
739+
1:
711740
wfe
712-
b __no_granule_support
741+
wfi
742+
b 1b
713743
ENDPROC(__no_granule_support)
714744

715745
__primary_switch:

arch/arm64/kernel/smp.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
* where to place its SVC stack
6464
*/
6565
struct secondary_data secondary_data;
66+
/* Number of CPUs which aren't online, but looping in kernel text. */
67+
int cpus_stuck_in_kernel;
6668

6769
enum ipi_msg_type {
6870
IPI_RESCHEDULE,
@@ -73,6 +75,16 @@ enum ipi_msg_type {
7375
IPI_WAKEUP
7476
};
7577

78+
#ifdef CONFIG_HOTPLUG_CPU
79+
static int op_cpu_kill(unsigned int cpu);
80+
#else
81+
static inline int op_cpu_kill(unsigned int cpu)
82+
{
83+
return -ENOSYS;
84+
}
85+
#endif
86+
87+
7688
/*
7789
* Boot a secondary CPU, and assign it the specified idle task.
7890
* This also gives us the initial stack to use for this CPU.
@@ -90,12 +102,14 @@ static DECLARE_COMPLETION(cpu_running);
90102
int __cpu_up(unsigned int cpu, struct task_struct *idle)
91103
{
92104
int ret;
105+
long status;
93106

94107
/*
95108
* We need to tell the secondary core where to find its stack and the
96109
* page tables.
97110
*/
98111
secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
112+
update_cpu_boot_status(CPU_MMU_OFF);
99113
__flush_dcache_area(&secondary_data, sizeof(secondary_data));
100114

101115
/*
@@ -119,6 +133,32 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
119133
}
120134

121135
secondary_data.stack = NULL;
136+
status = READ_ONCE(secondary_data.status);
137+
if (ret && status) {
138+
139+
if (status == CPU_MMU_OFF)
140+
status = READ_ONCE(__early_cpu_boot_status);
141+
142+
switch (status) {
143+
default:
144+
pr_err("CPU%u: failed in unknown state : 0x%lx\n",
145+
cpu, status);
146+
break;
147+
case CPU_KILL_ME:
148+
if (!op_cpu_kill(cpu)) {
149+
pr_crit("CPU%u: died during early boot\n", cpu);
150+
break;
151+
}
152+
/* Fall through */
153+
pr_crit("CPU%u: may not have shut down cleanly\n", cpu);
154+
case CPU_STUCK_IN_KERNEL:
155+
pr_crit("CPU%u: is stuck in kernel\n", cpu);
156+
cpus_stuck_in_kernel++;
157+
break;
158+
case CPU_PANIC_KERNEL:
159+
panic("CPU%u detected unsupported configuration\n", cpu);
160+
}
161+
}
122162

123163
return ret;
124164
}
@@ -184,6 +224,9 @@ asmlinkage void secondary_start_kernel(void)
184224
*/
185225
pr_info("CPU%u: Booted secondary processor [%08x]\n",
186226
cpu, read_cpuid_id());
227+
update_cpu_boot_status(CPU_BOOT_SUCCESS);
228+
/* Make sure the status update is visible before we complete */
229+
smp_wmb();
187230
set_cpu_online(cpu, true);
188231
complete(&cpu_running);
189232

@@ -325,10 +368,12 @@ void cpu_die_early(void)
325368
set_cpu_present(cpu, 0);
326369

327370
#ifdef CONFIG_HOTPLUG_CPU
371+
update_cpu_boot_status(CPU_KILL_ME);
328372
/* Check if we can park ourselves */
329373
if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_die)
330374
cpu_ops[cpu]->cpu_die(cpu);
331375
#endif
376+
update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
332377

333378
cpu_park_loop();
334379
}

0 commit comments

Comments
 (0)