Skip to contentSkip to conversation

arm64: Verify early CPU features

PATCH Superseded Suzuki K Poulose 4 participants 20 messages Jan 25, 2016 – Feb 9, 2016 arm64

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v4 cover arm64: Verify early CPU features

From: Suzuki K. Poulose <[email protected]>

This series adds support for verifying some of the cpufeatures that are decided early in the boot process based on the boot CPU and cannot be delayed until all the CPUs are up (e.g, ASIDBits and may be VHE?). It also adds support for handling the failures in booting the secondary CPUs which could not be synchronised with the master CPU, otherwise.

It also adds one of the users of this early hook, check for ASIDBits. The mm_context id is based on the ASIDBits width supported by the boot CPU and is used early in the initialisation. So we need to make sure that all the secondary CPUs supports the width reported by the booting CPU, failing which we crash the system.

This series has been tested on Juno, Fast model by injecting smaller ASIDBits, lack of page-size support and missing features(PAN)

Changes since V3:
- Sanitise the boot status handling code, removing unnecessary dsb()/isb(). (aka: write the code with a little bit more of thought), removed RFC tag.
- Store the status in head.txt when MMU turned off.

Changes since V2:
- Add support for synchronising the booting status of a secondary CPU. Added RFC Tag. Patch - 4
- Panic the system with incompatible ASIDBits

Suzuki K. Poulose (7):
arm64: Add a helper for parking CPUs in a loop
arm64: Introduce cpu_die_early
arm64: Move cpu_die_early to smp.c
arm64: Handle early CPU boot failures
arm64: Enable CPU capability verification unconditionally
arm64: Add helper for extracting ASIDBits
arm64: Ensure the secondary CPUs have safe ASIDBits size

 arch/arm64/include/asm/cpufeature.h  |    6 ---
 arch/arm64/include/asm/mmu_context.h |    2 +
 arch/arm64/include/asm/smp.h         |   35 +++++++++++++++++
 arch/arm64/kernel/asm-offsets.c      |    2 +
 arch/arm64/kernel/cpufeature.c       |   48 +++++++++--------------
 arch/arm64/kernel/head.S             |   39 +++++++++++++++++--
 arch/arm64/kernel/smp.c              |   69 ++++++++++++++++++++++++++++++++++
 arch/arm64/mm/context.c              |   51 ++++++++++++++++++-------
 8 files changed, 199 insertions(+), 53 deletions(-)
Signature · 1 line
1.7.9.5
#1 lore raw

PATCH v4 1/7 arm64: Add a helper for parking CPUs in a loop

From: Suzuki K. Poulose <[email protected]>

Adds a routine which can be used to park CPUs (spinning in kernel) when they can't be killed.

Cc Will Deacon <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Mark Rutland <[email protected]>
---
 arch/arm64/include/asm/smp.h   |    8 ++++++++
 arch/arm64/kernel/cpufeature.c |    5 +----
 2 files changed, 9 insertions(+), 4 deletions(-)
arch/arm64/include/asm/smp.h +8 -0
index d9c3d6a..53b53a9 100644
@@ -69,4 +69,12 @@ extern int __cpu_disable(void);
 extern void __cpu_die(unsigned int cpu);
 extern void cpu_die(void);
 
+static inline void cpu_park_loop(void)
+{
+	for (;;) {
+		wfe();
+		wfi();
+	}
+}
+
 #endif /* ifndef __ASM_SMP_H */
arch/arm64/kernel/cpufeature.c +1 -4
index 5c90aa4..cc4a089 100644
@@ -842,10 +842,7 @@ static void fail_incapable_cpu(char *cap_type,
 	/* Check if we can park ourselves */
 	if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_die)
 		cpu_ops[cpu]->cpu_die(cpu);
-	asm(
-	"1:	wfe\n"
-	"	wfi\n"
-	"	b	1b");
+	cpu_park_loop();
 }
 
 /*
Signature · 1 line
1.7.9.5
#2 lore raw

PATCH v4 2/7 arm64: Introduce cpu_die_early

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

Or in other words, make fail_incapable_cpu() reusable.

We use fail_incapable_cpu() to kill a secondary CPU early during the bringup, which doesn't have the system advertised capabilities. This patch makes the routine more generic, to kill a secondary booting CPU, getting rid of the dependency on capability struct. This can be used by checks which are not necessarily attached to a capability struct (e.g, cpu ASIDBits).

In that process, renames the function to cpu_die_early() to better match its functionality. This will be moved to arch/arm64/kernel/smp.c later.

Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/kernel/cpufeature.c |   24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)
arch/arm64/kernel/cpufeature.c +15 -9
index cc4a089..8275e7d 100644
@@ -827,15 +827,15 @@ static u64 __raw_read_system_reg(u32 sys_id)
 }
 
 /*
- * Park the CPU which doesn't have the capability as advertised
- * by the system.
+ * Kill the calling secondary CPU, early in bringup before it is turned
+ * online.
  */
-static void fail_incapable_cpu(char *cap_type,
-				 const struct arm64_cpu_capabilities *cap)
+void cpu_die_early(void)
 {
 	int cpu = smp_processor_id();
 
-	pr_crit("CPU%d: missing %s : %s\n", cpu, cap_type, cap->desc);
+	pr_crit("CPU%d: will not boot\n", cpu);
+
 	/* Mark this CPU absent */
 	set_cpu_present(cpu, 0);
 
@@ -873,8 +873,11 @@ void verify_local_cpu_capabilities(void)
 		 * If the new CPU misses an advertised feature, we cannot proceed
 		 * further, park the cpu.
 		 */
-		if (!feature_matches(__raw_read_system_reg(caps[i].sys_reg), &caps[i]))
-			fail_incapable_cpu("arm64_features", &caps[i]);
+		if (!feature_matches(__raw_read_system_reg(caps[i].sys_reg), &caps[i])) {
+			pr_crit("CPU%d: missing feature: %s\n",
+					smp_processor_id(), caps[i].desc);
+			cpu_die_early();
+		}
 		if (caps[i].enable)
 			caps[i].enable(NULL);
 	}
@@ -882,8 +885,11 @@ void verify_local_cpu_capabilities(void)
 	for (i = 0, caps = arm64_hwcaps; caps[i].desc; i++) {
 		if (!cpus_have_hwcap(&caps[i]))
 			continue;
-		if (!feature_matches(__raw_read_system_reg(caps[i].sys_reg), &caps[i]))
-			fail_incapable_cpu("arm64_hwcaps", &caps[i]);
+		if (!feature_matches(__raw_read_system_reg(caps[i].sys_reg), &caps[i])) {
+			pr_crit("CPU%d: missing HWCAP: %s\n",
+					smp_processor_id(), caps[i].desc);
+			cpu_die_early();
+		}
 	}
 }
 
Signature · 1 line
1.7.9.5
#3 lore raw

PATCH v4 3/7 arm64: Move cpu_die_early to smp.c

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

This patch moves cpu_die_early to smp.c, where it fits better. No functional changes, except for adding the necessary checks for CONFIG_HOTPLUG_CPU.

Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/include/asm/smp.h   |    1 +
 arch/arm64/kernel/cpufeature.c |   19 -------------------
 arch/arm64/kernel/smp.c        |   22 ++++++++++++++++++++++
 3 files changed, 23 insertions(+), 19 deletions(-)
arch/arm64/include/asm/smp.h +1 -0
index 53b53a9..32e75ee 100644
@@ -68,6 +68,7 @@ extern int __cpu_disable(void);
 
 extern void __cpu_die(unsigned int cpu);
 extern void cpu_die(void);
+extern void cpu_die_early(void);
 
 static inline void cpu_park_loop(void)
 {
arch/arm64/kernel/cpufeature.c +0 -19
index 8275e7d..60c3f47 100644
@@ -827,25 +827,6 @@ static u64 __raw_read_system_reg(u32 sys_id)
 }
 
 /*
- * Kill the calling secondary CPU, early in bringup before it is turned
- * online.
- */
-void cpu_die_early(void)
-{
-	int cpu = smp_processor_id();
-
-	pr_crit("CPU%d: will not boot\n", cpu);
-
-	/* Mark this CPU absent */
-	set_cpu_present(cpu, 0);
-
-	/* Check if we can park ourselves */
-	if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_die)
-		cpu_ops[cpu]->cpu_die(cpu);
-	cpu_park_loop();
-}
-
-/*
  * Run through the enabled system capabilities and enable() it on this CPU.
  * The capabilities were decided based on the available CPUs at the boot time.
  * Any new CPU should match the system wide status of the capability. If the
arch/arm64/kernel/smp.c +22 -0
index b1adc51..59f032b 100644
@@ -313,6 +313,28 @@ void cpu_die(void)
 }
 #endif
 
+/*
+ * Kill the calling secondary CPU, early in bringup before it is turned
+ * online.
+ */
+void cpu_die_early(void)
+{
+	int cpu = smp_processor_id();
+
+	pr_crit("CPU%d: will not boot\n", cpu);
+
+	/* Mark this CPU absent */
+	set_cpu_present(cpu, 0);
+
+#ifdef CONFIG_HOTPLUG_CPU
+	/* Check if we can park ourselves */
+	if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_die)
+		cpu_ops[cpu]->cpu_die(cpu);
+#endif
+
+	cpu_park_loop();
+}
+
 static void __init hyp_mode_check(void)
 {
 	if (is_hyp_mode_available())
Signature · 1 line
1.7.9.5
#4 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

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 in __early_cpu_boot_status (reserved in head.txt section)

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 Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/include/asm/smp.h    |   26 ++++++++++++++++++++++
 arch/arm64/kernel/asm-offsets.c |    2 ++
 arch/arm64/kernel/head.S        |   39 +++++++++++++++++++++++++++++---
 arch/arm64/kernel/smp.c         |   47 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 111 insertions(+), 3 deletions(-)
arch/arm64/include/asm/smp.h +26 -0
index 32e75ee..6548769 100644
@@ -16,6 +16,19 @@
 #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;
+	int status;
 };
+
 extern struct secondary_data secondary_data;
+extern int __early_cpu_boot_status;
 extern void secondary_entry(void);
 
 extern void arch_send_call_function_single_ipi(int cpu);
@@ -78,4 +97,11 @@ static inline void cpu_park_loop(void)
 	}
 }
 
+static inline void update_cpu_boot_status(int val)
+{
+	WRITE_ONCE(secondary_data.status, val);
+}
+
+#endif /* ifndef __ASSEMBLY__ */
+
 #endif /* ifndef __ASM_SMP_H */
arch/arm64/kernel/asm-offsets.c +2 -0
index fffa4ac6..463fcf1 100644
@@ -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));
arch/arm64/kernel/head.S +36 -3
index ffe9c2b..16bfa34 100644
@@ -34,6 +34,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>
@@ -606,7 +607,7 @@ ENTRY(secondary_startup)
 ENDPROC(secondary_startup)
 
 ENTRY(__secondary_switched)
-	ldr	x0, [x21]			// get secondary_data.stack
+	ldr	x0, [x21, #CPU_BOOT_STACK]	// get secondary_data.stack
 	mov	sp, x0
 	and	x0, x0, #~(THREAD_SIZE - 1)
 	msr	sp_el0, x0			// save thread_info
@@ -615,6 +616,32 @@ ENTRY(__secondary_switched)
 ENDPROC(__secondary_switched)
 
 /*
+ * The booting CPU updates the failed status, with MMU turned off,
+ * below which lies in head.txt to make sure it doesn't share the same writeback
+ * granule. So that we can invalidate it properly.
+ *
+ * update_early_cpu_boot_status tmp, status
+ *  - Corrupts tmp, x0, x1
+ *  - Writes 'status' to __early_cpu_boot_status and makes sure
+ *    it is committed to memory.
+ */
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm
+
+ENTRY(__early_cpu_boot_status)
+	.long 	0
+END(__early_cpu_boot_status)
+
+/*
  * Enable the MMU.
  *
  *  x0  = SCTLR_EL1 value for turning on the MMU.
@@ -631,12 +658,14 @@ __enable_mmu:
 	ubfx	x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
 	cmp	x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
 	b.ne	__no_granule_support
+	mov	x4, x0
+	update_early_cpu_boot_status x2, 0
 	ldr	x5, =vectors
 	msr	vbar_el1, x5
 	msr	ttbr0_el1, x25			// load TTBR0
 	msr	ttbr1_el1, x26			// load TTBR1
 	isb
-	msr	sctlr_el1, x0
+	msr	sctlr_el1, x4
 	isb
 	/*
 	 * Invalidate the local I-cache so that any instructions fetched
@@ -650,6 +679,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 x2, CPU_STUCK_IN_KERNEL
+1:
 	wfe
-	b __no_granule_support
+	wfi
+	b 1b
 ENDPROC(__no_granule_support)
arch/arm64/kernel/smp.c +47 -0

94 lines not on this page · raw message

Signature · 1 line
1.7.9.5
maintainer
#5 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Hi Suzuki,

arch/arm64/include/asm/smp.h line 28

Quoted 10 lines · Suzuki K. Poulose
arch/arm64/include/asm/smp.h +10 -0
+/* 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

Please add braces around these numbers, just in case (I added them locally).

Quoted 4 lines

/*
+ * The booting CPU updates the failed status, with MMU turned off,
+ * below which lies in head.txt to make sure it doesn't share the same writeback
+ * granule. So that we can invalidate it properly.

I can't really parse this (it looks like punctuation in the wrong place; also "share the same..." with what?).

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 11 lines

+ *
+ * update_early_cpu_boot_status tmp, status
+ * - Corrupts tmp, x0, x1
+ * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * it is committed to memory.
+ */

arch/arm64/kernel/head.S +5 -0
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 638

Quoted 6 lines
arch/arm64/kernel/head.S +6 -0
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm

If the CPU that's currently booting has the MMU off, what's the point of invalidating the cache here? The operation may not even be broadcast to the other CPU. So you actually need the invalidation before reading the status on the primary CPU.

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 642

Quoted 4 lines
arch/arm64/kernel/head.S +4 -0
+
+ENTRY(__early_cpu_boot_status)
+	.long 	0
+END(__early_cpu_boot_status)

I think we should just do like __boot_cpu_mode and place it in the .data..cacheline_aligned section. You can always use the safe clean+invalidate before reading the value so that we don't care much about the write-back granule.

Quoted 16 lines
arch/arm64/kernel/smp.c +2 -0
@@ -89,12 +101,14 @@ static DECLARE_COMPLETION(cpu_running);
 int __cpu_up(unsigned int cpu, struct task_struct *idle)
 {
 	int ret;
+	int 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));
 
 	/*

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 135

Quoted 5 lines
arch/arm64/kernel/smp.c +2 -0
@@ -117,7 +131,35 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
 		pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
 	}
 
+	/* Make sure the update to status is visible */
+	smp_rmb();

Which status? In relation to what?

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 141

Quoted 6 lines
arch/arm64/kernel/smp.c +5 -0
 	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);

You need cache maintenance before reading this.

Quoted 14 lines
arch/arm64/kernel/smp.c +14 -0
+
+		switch (status) {
+		default:
+			pr_err("CPU%u: failed in unknown state : 0x%x\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:

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 165

Quoted 10 lines
arch/arm64/kernel/smp.c +7 -0
+			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;
 }

BTW, you can send a fix-up on top of this series with corrections, I can fold them in.

Signature · 1 line
Catalin
reviewer
#6 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 16 lines · Catalin Marinas

Hi Suzuki,

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 10 lines · Suzuki K. Poulose

+ * update_early_cpu_boot_status tmp, status
+ * - Corrupts tmp, x0, x1
+ * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * it is committed to memory.
+ */

arch/arm64/kernel/head.S +5 -0
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

Quoted 9 lines

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 638

Quoted 6 lines
arch/arm64/kernel/head.S +6 -0
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm

If the CPU that's currently booting has the MMU off, what's the point of invalidating the cache here?

To invalidate stale lines for this address, held in any caches prior to the PoC. I'm assuming that __early_cpu_boot_status is sufficiently padded to the CWG.

Cache maintenance works when SCTLR_ELx.M == 0, though barriers are required prior to cache maintenance as non-cacheable accesses do not hazard by VA.

The MMU being off has no effect on the cache maintenance itself.

Quoted 3 lines

The operation may not even be broadcast to the other CPU. So you actually need the invalidation before reading the status on the primary CPU.

We require that CPUs are coherent when they enter the kernel, so any cache maintenance operation _must_ affect all coherent caches (i.e. it must be broadcast and must affect all coherent caches prior to the PoC in this case).

Quoted 7 lines

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 642

Quoted 4 lines
arch/arm64/kernel/head.S +4 -0
+
+ENTRY(__early_cpu_boot_status)
+	.long 	0
+END(__early_cpu_boot_status)

I think we should just do like __boot_cpu_mode and place it in the .data..cacheline_aligned section.

I think we should add a separate __writeback_aligned annotation for stuff like this, even if it falls in .data..cacheline_aligned for now.

Otherwise, agreed.

Quoted 2 lines

You can always use the safe clean+invalidate before reading the value so that we don't care much about the write-back granule.

To get correct data out we need to pad to the CQG regardless of whether the reader or the writer perform the maintenance.

Mark.

maintainer
#7 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 25 lines · Mark Rutland
Quoted 14 lines · Catalin Marinas

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 10 lines · Suzuki K. Poulose

+ * update_early_cpu_boot_status tmp, status
+ * - Corrupts tmp, x0, x1
+ * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * it is committed to memory.
+ */

arch/arm64/kernel/head.S +5 -0
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

Quoted 9 lines

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 638

Quoted 6 lines
arch/arm64/kernel/head.S +6 -0
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm

If the CPU that's currently booting has the MMU off, what's the point of invalidating the cache here?

Quoted 3 lines

To invalidate stale lines for this address, held in any caches prior to the PoC. I'm assuming that __early_cpu_boot_status is sufficiently padded to the CWG.

I would have rather invalidated it before writing the [x0], if that's what it's aimed at.

Quoted 5 lines

Cache maintenance works when SCTLR_ELx.M == 0, though barriers are required prior to cache maintenance as non-cacheable accesses do not hazard by VA.

The MMU being off has no effect on the cache maintenance itself.

I know, but whether it has an effect on other CPUs is a different question (it probably has). Anyway, I would rather do the invalidation on the CPU that actually reads this status.

Quoted 8 lines
Quoted 3 lines

The operation may not even be broadcast to the other CPU. So you actually need the invalidation before reading the status on the primary CPU.

We require that CPUs are coherent when they enter the kernel, so any cache maintenance operation _must_ affect all coherent caches (i.e. it must be broadcast and must affect all coherent caches prior to the PoC in this case).

In general, if you perform cache maintenance on a non-shareable mapping, I don't think it would be broadcast. But in this case, the MMU is off, data accesses default to Device_nGnRnE and considered outer shareable, so it may actually work. Is this stated anywhere in the ARM ARM?

But see my point above about invalidating on the secondary CPU before writing the location and invalidating again on the primary CPU before reading it.

Signature · 1 line
Catalin
reviewer
#8 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 26 lines · Catalin Marinas
Quoted 25 lines · Mark Rutland
Quoted 23 lines · Catalin Marinas

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 10 lines · Suzuki K. Poulose

+ * update_early_cpu_boot_status tmp, status
+ * - Corrupts tmp, x0, x1
+ * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * it is committed to memory.
+ */

arch/arm64/kernel/head.S +5 -0
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 638

Quoted 6 lines
arch/arm64/kernel/head.S +6 -0
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm

If the CPU that's currently booting has the MMU off, what's the point of invalidating the cache here?

Quoted 6 lines
Quoted 3 lines

To invalidate stale lines for this address, held in any caches prior to the PoC. I'm assuming that __early_cpu_boot_status is sufficiently padded to the CWG.

I would have rather invalidated it before writing the [x0], if that's what it's aimed at.

That alone wouldn't not be sufficient, due to speculative fetches allocating new (clean) lines prior to the write completing.

I was expecting the CWG-aligned region to only be written to with the MMU off, i.e. we'd only have clean stale lines and no dirty lines.

Quoted 9 lines
Quoted 5 lines

Cache maintenance works when SCTLR_ELx.M == 0, though barriers are required prior to cache maintenance as non-cacheable accesses do not hazard by VA.

The MMU being off has no effect on the cache maintenance itself.

I know, but whether it has an effect on other CPUs is a different question (it probably has). Anyway, I would rather do the invalidation on the CPU that actually reads this status.

My only worry would be how this gets ordered against the (non-cacheable) store. I guess we'd complete that with a DSB SY regardless.

Given that, I have no problem doing the invalidate on the read side. Assuming we only write from the side with the MMU off, we don't need maintenance on that side.

Quoted 9 lines
Quoted 8 lines
Quoted 3 lines

The operation may not even be broadcast to the other CPU. So you actually need the invalidation before reading the status on the primary CPU.

We require that CPUs are coherent when they enter the kernel, so any cache maintenance operation _must_ affect all coherent caches (i.e. it must be broadcast and must affect all coherent caches prior to the PoC in this case).

Quoted 4 lines

In general, if you perform cache maintenance on a non-shareable mapping, I don't think it would be broadcast. But in this case, the MMU is off, data accesses default to Device_nGnRnE and considered outer shareable, so it may actually work. Is this stated anywhere in the ARM ARM?

In ARM DDI 0487A.h, D4.2.8 "The effects of disabling a stage of address translation" we state:

Cache maintenance instructions act on the target cache regardless of whether any stages of address translation are disabled, and regardless of the values of the memory attributes. However, if a stage of address translation is disabled, they use the flat address mapping for that translation stage.

Thanks,
Mark.

maintainer
#9 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 31 lines · Mark Rutland
Quoted 30 lines · Catalin Marinas
Quoted 28 lines · Mark Rutland
Quoted 14 lines · Catalin Marinas

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 10 lines · Suzuki K. Poulose

+ * update_early_cpu_boot_status tmp, status
+ * - Corrupts tmp, x0, x1
+ * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * it is committed to memory.
+ */

arch/arm64/kernel/head.S +5 -0
+
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

Quoted 9 lines

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 638

Quoted 6 lines
arch/arm64/kernel/head.S +6 -0
+	mov	x1, #\status
+	str	x1, [x0]
+	add	x1, x0, 4
+	bl	__inval_cache_range
+	mov	lr, \tmp
+	.endm

If the CPU that's currently booting has the MMU off, what's the point of invalidating the cache here?

To invalidate stale lines for this address, held in any caches prior to the PoC. I'm assuming that __early_cpu_boot_status is sufficiently padded to the CWG.

Quoted 5 lines
Quoted 2 lines

I would have rather invalidated it before writing the [x0], if that's what it's aimed at.

That alone wouldn't not be sufficient, due to speculative fetches allocating new (clean) lines prior to the write completing.

Indeed.

Quoted 2 lines

I was expecting the CWG-aligned region to only be written to with the MMU off, i.e. we'd only have clean stale lines and no dirty lines.

I assume that's true even in a guest.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Cache maintenance works when SCTLR_ELx.M == 0, though barriers are required prior to cache maintenance as non-cacheable accesses do not hazard by VA.

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

The MMU being off has no effect on the cache maintenance itself.

I know, but whether it has an effect on other CPUs is a different question (it probably has). Anyway, I would rather do the invalidation on the CPU that actually reads this status.

My only worry would be how this gets ordered against the (non-cacheable) store. I guess we'd complete that with a DSB SY regardless.

The synchronisation on the primary CPU is a bit flaky anyway and just relies on long timeouts. It waits for a while to complete (1 sec) and than it reads the status. So it assumes that the secondary CPU finished its writing but neither DSB nor cache maintenance guarantee this.

Quoted 3 lines

Given that, I have no problem doing the invalidate on the read side. Assuming we only write from the side with the MMU off, we don't need maintenance on that side.

I agree.

Quoted 14 lines
Quoted 9 lines
Quoted 8 lines
Quoted 3 lines

The operation may not even be broadcast to the other CPU. So you actually need the invalidation before reading the status on the primary CPU.

We require that CPUs are coherent when they enter the kernel, so any cache maintenance operation _must_ affect all coherent caches (i.e. it must be broadcast and must affect all coherent caches prior to the PoC in this case).

Quoted 4 lines

In general, if you perform cache maintenance on a non-shareable mapping, I don't think it would be broadcast. But in this case, the MMU is off, data accesses default to Device_nGnRnE and considered outer shareable, so it may actually work. Is this stated anywhere in the ARM ARM?

Quoted 8 lines

In ARM DDI 0487A.h, D4.2.8 "The effects of disabling a stage of address translation" we state:

Cache maintenance instructions act on the target cache regardless of whether any stages of address translation are disabled, and regardless of the values of the memory attributes. However, if a stage of address translation is disabled, they use the flat address mapping for that translation stage.

But does "target cache" include other CPUs in the system?

Signature · 1 line
Catalin
reviewer
#10 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 10 lines · Catalin Marinas
Quoted 9 lines · Mark Rutland
Quoted 4 lines · Catalin Marinas

In general, if you perform cache maintenance on a non-shareable mapping, I don't think it would be broadcast. But in this case, the MMU is off, data accesses default to Device_nGnRnE and considered outer shareable, so it may actually work. Is this stated anywhere in the ARM ARM?

In ARM DDI 0487A.h, D4.2.8 "The effects of disabling a stage of address translation" we state:

Quoted 7 lines
Quoted 5 lines

Cache maintenance instructions act on the target cache regardless of whether any stages of address translation are disabled, and regardless of the values of the memory attributes. However, if a stage of address translation is disabled, they use the flat address mapping for that translation stage.

But does "target cache" include other CPUs in the system?

It appears so. There's a more complete description (with a table) on page D3-1718, "Effects of instructions that operate by VA to the Point of Coherency", where it's explicitly stated:

For Device memory and Normal memory that is Inner Non-cacheable, Outer Non-cacheable, these instructions must affect the caches of all PEs in the Outer Shareable shareability domain of the PE on which the instruction is operating.

In other cases, per the table, the maintenance may be limited to a smaller set of caches.

Thanks,
Mark.

#11 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

In reply to Catalin Marinas

Hi Catalin,

Quoted 5 lines

arch/arm64/include/asm/smp.h line 28

Quoted 2 lines
arch/arm64/include/asm/smp.h +2 -0
+/* Fatal system error detected by secondary CPU, crash the system */
+#define CPU_PANIC_KERNEL	3

Please add braces around these numbers, just in case (I added them locally).

OK, I will base my changes on top of the corrections.

Quoted 8 lines
Quoted 5 lines

/*
+ * The booting CPU updates the failed status, with MMU turned off,
+ * below which lies in head.txt to make sure it doesn't share the same writeback
+ * granule. So that we can invalidate it properly.

I can't really parse this (it looks like punctuation in the wrong place; also "share the same..." with what?).

Sorry it doesn't parse :(. It should have been something like :

"The booting CPU updates the failed status @__early_cpu_boot_status (with the MMU turned off). It is placed in head.txt to make sure it doesn't share the same write back granule with anything else while the CPU updates it."

Quoted 6 lines

arch/arm64/kernel/head.S ENTRY(__secondary_switched) · line 632

Quoted 4 lines
arch/arm64/kernel/head.S +4 -0
+	.macro	update_early_cpu_boot_status tmp, status
+	mov	\tmp, lr
+	adrp	x0, __early_cpu_boot_status
+	add	x0, x0, #:lo12:__early_cpu_boot_status

Nitpick: you could use the adr_l macro.

Yes, that looks much better, will keep in mind.

Quoted 8 lines

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 135

Quoted 6 lines
arch/arm64/kernel/smp.c +2 -0
@@ -117,7 +131,35 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
  		pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
  	}
 
+	/* Make sure the update to status is visible */
+	smp_rmb();

Which status? In relation to what?

The secondary_data.status updated by the successful CPUs which have mmu turned on, and updating the cpu_running. But as Mark pointed out, complete() implies a barrier, hence won't need it.

Quoted 9 lines

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 141

Quoted 7 lines
arch/arm64/kernel/smp.c +5 -0
  	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);

You need cache maintenance before reading this.

OK.

Thanks
Suzuki

reviewer
#12 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

In reply to Suzuki K Poulose
Quoted 30 lines · Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

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 in __early_cpu_boot_status (reserved in head.txt section)

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.

Quoted 8 lines

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.

When would we use this last case?

Perhaps a better option is to always throw any incompatible CPU into an (MMU-off) pen, and assume that it's stuck in the kernel, even if we could theoretically turn it off.

A system with incompatible CPUs is a broken system to begin with...

Otherwise, comments below.

Quoted 10 lines
Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/include/asm/smp.h    |   26 ++++++++++++++++++++++
 arch/arm64/kernel/asm-offsets.c |    2 ++
 arch/arm64/kernel/head.S        |   39 +++++++++++++++++++++++++++++---
 arch/arm64/kernel/smp.c         |   47 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 111 insertions(+), 3 deletions(-)

[...]

arch/arm64/kernel/head.S __enable_mmu: · line 687

Quoted 11 lines
arch/arm64/kernel/head.S +5 -1
@@ -650,6 +679,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 x2, CPU_STUCK_IN_KERNEL
+1:
 	wfe
-	b __no_granule_support
+	wfi
+	b 1b

The addition of wfi seems fine, but should be mentioned in the commit message.

arch/arm64/kernel/smp.c line 67

Quoted 11 lines
arch/arm64/kernel/head.S +0 -0
 ENDPROC(__no_granule_support)
arch/arm64/kernel/smp.c +2 -0
index 59f032b..d2721ae 100644
@@ -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. */
+u32 cpus_stuck_in_kernel;

Why u32 rather than int?

Quoted 5 lines
arch/arm64/kernel/smp.c +0 -0
 enum ipi_msg_type {
 	IPI_RESCHEDULE,
@@ -72,6 +74,16 @@ enum ipi_msg_type {
 	IPI_IRQ_WORK,

arch/arm64/kernel/smp.c enum ipi_msg_type { · line 84

Quoted 10 lines
arch/arm64/kernel/smp.c +8 -0
 };
 
+#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

There is no !CONFIG_HOTPLUG_CPU configuration any more.

Quoted 22 lines
arch/arm64/kernel/smp.c +4 -0
+
+
 /*
  * Boot a secondary CPU, and assign it the specified idle task.
  * This also gives us the initial stack to use for this CPU.
@@ -89,12 +101,14 @@ static DECLARE_COMPLETION(cpu_running);
 int __cpu_up(unsigned int cpu, struct task_struct *idle)
 {
 	int ret;
+	int 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));
 
 	/*

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 137

Quoted 7 lines
arch/arm64/kernel/smp.c +3 -0
@@ -117,7 +131,35 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
 		pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
 	}
 
+	/* Make sure the update to status is visible */
+	smp_rmb();
 	secondary_data.stack = NULL;
+	status = READ_ONCE(secondary_data.status);

What is the rmb intended to order here?

Quoted 29 lines
arch/arm64/kernel/smp.c +25 -0
+	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%x\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;
 }

arch/arm64/kernel/smp.c asmlinkage void secondary_start_kernel(void) · line 232

Quoted 6 lines
arch/arm64/kernel/smp.c +3 -0
@@ -185,6 +227,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();

Surely complete() has appropriate barriers?

arch/arm64/kernel/smp.c void cpu_die_early(void) · line 378

Quoted 11 lines
arch/arm64/kernel/smp.c +1 -0
 	set_cpu_online(cpu, true);
 	complete(&cpu_running);
 
@@ -327,10 +372,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);

I think you need a barrier to ensure visibility of the store prior to calling cpu_die (i.e. you want to order an access against execution). A dsb is what you want -- smp_wmb() only expands to a dmb.

arch/arm64/kernel/smp.c void cpu_die_early(void) · line 382

Quoted 4 lines
arch/arm64/kernel/smp.c +1 -0
 #endif
+	update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
 
 	cpu_park_loop();

Likewise here.

Mark.

maintainer
#13 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 35 lines · Mark Rutland
Quoted 34 lines · Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

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 in __early_cpu_boot_status (reserved in head.txt section)

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.

Quoted 6 lines
Quoted 4 lines

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.

When would we use this last case?

It's used in a subsequent series when verifying the ASID bits. I haven't followed the previous discussions but I guess Suzuki aims to panic the whole kernel rather than just stop the current CPU when incompatible ASID size is found.

Signature · 1 line
Catalin
#14 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

In reply to Mark Rutland
Quoted 10 lines · Mark Rutland
Quoted 7 lines · Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

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.

When would we use this last case?

As of now, it is used when we have incompatible ASID bits.

Quoted 4 lines

Perhaps a better option is to always throw any incompatible CPU into an (MMU-off) pen, and assume that it's stuck in the kernel, even if we could theoretically turn it off.

Right, that is another option. I am fine with either.

Quoted 6 lines

arch/arm64/kernel/head.S __enable_mmu: · line 687

Quoted 3 lines
arch/arm64/kernel/head.S +2 -1
-	b __no_granule_support
+	wfi
+	b 1b

The addition of wfi seems fine, but should be mentioned in the commit message.

Sure.

Quoted 5 lines

arch/arm64/kernel/smp.c line 67

Quoted 3 lines

struct secondary_data secondary_data;

arch/arm64/kernel/smp.c +2 -0
+/* Number of CPUs which aren't online, but looping in kernel text. */
+u32 cpus_stuck_in_kernel;

Why u32 rather than int?

No specific reasons, since it is going to be a quantity, which cannot be < 0, kept it unsigned. It could be unsigned int.

Quoted 10 lines

arch/arm64/kernel/smp.c enum ipi_msg_type { · line 84

Quoted 8 lines
arch/arm64/kernel/smp.c +8 -0
+#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

There is no !CONFIG_HOTPLUG_CPU configuration any more.

Thats what I thought but then there was [1]. If you disable CONFIG_PM_SLEEP, you can still build with !CONFIG_HOTPLUG_CPU (or in other words allnoconfig)

[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-November/384589.html

Quoted 7 lines

arch/arm64/kernel/smp.c int __cpu_up(unsigned int cpu, struct task_struct *idle) · line 137

Quoted 5 lines
arch/arm64/kernel/smp.c +3 -0
+	/* Make sure the update to status is visible */
+	smp_rmb();
  	secondary_data.stack = NULL;
+	status = READ_ONCE(secondary_data.status);

What is the rmb intended to order here?

It was for the complete(). But...

Quoted 5 lines

arch/arm64/kernel/smp.c asmlinkage void secondary_start_kernel(void) · line 232

Quoted 3 lines
arch/arm64/kernel/smp.c +3 -0
+	update_cpu_boot_status(CPU_BOOT_SUCCESS);
+	/* Make sure the status update is visible before we complete */
+	smp_wmb();

Surely complete() has appropriate barriers?

Yes, it does. We can remove it.

Quoted 10 lines

arch/arm64/kernel/smp.c void cpu_die_early(void) · line 378

Quoted 5 lines
  #ifdef CONFIG_HOTPLUG_CPU
arch/arm64/kernel/smp.c +1 -0
+	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);

I think you need a barrier to ensure visibility of the store prior to calling cpu_die (i.e. you want to order an access against execution). A dsb is what you want -- smp_wmb() only expands to a dmb.

OK.

Quoted 6 lines

arch/arm64/kernel/smp.c void cpu_die_early(void) · line 382

Quoted 4 lines
  #endif
arch/arm64/kernel/smp.c +1 -0
+	update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
 
  	cpu_park_loop();

Likewise here.

OK.

Thanks
Suzuki

reviewer
#15 lore raw

PATCH v4 4/7 arm64: Handle early CPU boot failures

Quoted 3 lines · Suzuki K. Poulose
Quoted 2 lines · Mark Rutland
Quoted 1 line · Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

[...]

Quoted 8 lines
Quoted 5 lines

arch/arm64/kernel/smp.c line 67

Quoted 3 lines

struct secondary_data secondary_data;

arch/arm64/kernel/smp.c +2 -0
+/* Number of CPUs which aren't online, but looping in kernel text. */
+u32 cpus_stuck_in_kernel;

Why u32 rather than int?

No specific reasons, since it is going to be a quantity, which cannot be < 0, kept it unsigned. It could be unsigned int.

Elsewhere, int or unsigned int is used to contain a cpu number. I think either would be preferable to u32, to at least limit the inconsistency.

Quoted 11 lines
Quoted 10 lines

arch/arm64/kernel/smp.c enum ipi_msg_type { · line 84

Quoted 8 lines
arch/arm64/kernel/smp.c +8 -0
+#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

There is no !CONFIG_HOTPLUG_CPU configuration any more.

Quoted 4 lines

Thats what I thought but then there was [1]. If you disable CONFIG_PM_SLEEP, you can still build with !CONFIG_HOTPLUG_CPU (or in other words allnoconfig)

[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-November/384589.html

Aargh, indeed. I had confused CONFIG_HOTPLUG_CPU and CONFIG_SMP. Sorry!

Mark.

#16 lore raw

PATCH v4 5/7 arm64: Enable CPU capability verification unconditionally

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

We verify the capabilities of the secondary CPUs only when hotplug is enabled. The boot time activated CPUs do not go through the verification by checking whether the system wide capabilities were initialised or not.

This patch removes the capability check dependency on CONFIG_HOTPLUG_CPU, to make sure that all the secondary CPUs go through the check. The boot time activated CPUs will still skip the system wide capability check. The plan is to hook in a check for CPU features used by the kernel at early boot up, based on the Boot CPU values.

Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/include/asm/cpufeature.h |    6 ------
 arch/arm64/kernel/cpufeature.c      |   10 ----------
 2 files changed, 16 deletions(-)
arch/arm64/include/asm/cpufeature.h +0 -6
index 8f271b8..26024c4 100644
@@ -164,13 +164,7 @@ void update_cpu_capabilities(const struct arm64_cpu_capabilities *caps,
 			    const char *info);
 void check_local_cpu_errata(void);
 
-#ifdef CONFIG_HOTPLUG_CPU
 void verify_local_cpu_capabilities(void);
-#else
-static inline void verify_local_cpu_capabilities(void)
-{
-}
-#endif
 
 u64 read_system_reg(u32 id);
 
arch/arm64/kernel/cpufeature.c +0 -10
index 60c3f47..5ba3ef1 100644
@@ -768,8 +768,6 @@ enable_cpu_capabilities(const struct arm64_cpu_capabilities *caps)
 			on_each_cpu(caps[i].enable, NULL, true);
 }
 
-#ifdef CONFIG_HOTPLUG_CPU
-
 /*
  * Flag to indicate if we have computed the system wide
  * capabilities based on the boot time active CPUs. This
@@ -874,14 +872,6 @@ void verify_local_cpu_capabilities(void)
 	}
 }
 
-#else	/* !CONFIG_HOTPLUG_CPU */
-
-static inline void set_sys_caps_initialised(void)
-{
-}
-
-#endif	/* CONFIG_HOTPLUG_CPU */
-
 static void __init setup_feature_capabilities(void)
 {
 	update_cpu_capabilities(arm64_features, "detected feature:");
Signature · 1 line
1.7.9.5
#17 lore raw

PATCH v4 6/7 arm64: Add helper for extracting ASIDBits

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

Add a helper to extract ASIDBits on the current cpu

Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/mm/context.c |   36 +++++++++++++++++++++++-------------
 1 file changed, 23 insertions(+), 13 deletions(-)
arch/arm64/mm/context.c +23 -13
index e87f53f..643bf4b 100644
@@ -40,6 +40,28 @@ static cpumask_t tlb_flush_pending;
 #define ASID_FIRST_VERSION	(1UL << asid_bits)
 #define NUM_USER_ASIDS		ASID_FIRST_VERSION
 
+/* Get the ASIDBits supported by the current CPU */
+static u32 get_cpu_asid_bits(void)
+{
+	u32 asid;
+	int fld = cpuid_feature_extract_field(read_cpuid(ID_AA64MMFR0_EL1),
+						ID_AA64MMFR0_ASID_SHIFT);
+
+	switch (fld) {
+	default:
+		pr_warn("CPU%d: Unknown ASID size (%d); assuming 8-bit\n",
+					smp_processor_id(),  fld);
+		/* Fallthrough */
+	case 0:
+		asid = 8;
+		break;
+	case 2:
+		asid = 16;
+	}
+
+	return asid;
+}
+
 static void flush_context(unsigned int cpu)
 {
 	int i;
@@ -187,19 +209,7 @@ switch_mm_fastpath:
 
 static int asids_init(void)
 {
-	int fld = cpuid_feature_extract_field(read_cpuid(ID_AA64MMFR0_EL1), 4);
-
-	switch (fld) {
-	default:
-		pr_warn("Unknown ASID size (%d); assuming 8-bit\n", fld);
-		/* Fallthrough */
-	case 0:
-		asid_bits = 8;
-		break;
-	case 2:
-		asid_bits = 16;
-	}
-
+	asid_bits = get_cpu_asid_bits();
 	/* If we end up with more CPUs than ASIDs, expect things to crash */
 	WARN_ON(NUM_USER_ASIDS < num_possible_cpus());
 	atomic64_set(&asid_generation, ASID_FIRST_VERSION);
Signature · 1 line
1.7.9.5
#18 lore raw

PATCH v4 7/7 arm64: Ensure the secondary CPUs have safe ASIDBits size

In reply to Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

Adds a hook for checking whether a secondary CPU has the features used already by the kernel during early boot, based on the boot CPU and plugs in the check for ASID size.

The ID_AA64MMFR0_EL1:ASIDBits determines the size of the mm context id and is used in the early boot to make decisions. The value is picked up from the Boot CPU and cannot be delayed until other CPUs are up. If a secondary CPU has a smaller size than that of the Boot CPU, things will break horribly and the usual SANITY check is not good enough to prevent the system from crashing. So, crash the system with enough information.

Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Catalin Marinas <[email protected]>
---
 arch/arm64/include/asm/mmu_context.h |    2 ++
 arch/arm64/kernel/cpufeature.c       |   12 ++++++++++++
 arch/arm64/mm/context.c              |   15 +++++++++++++++
 3 files changed, 29 insertions(+)
arch/arm64/include/asm/mmu_context.h +2 -0
index 2416578..bd8a0b9 100644
@@ -147,4 +147,6 @@ switch_mm(struct mm_struct *prev, struct mm_struct *next,
 #define deactivate_mm(tsk,mm)	do { } while (0)
 #define activate_mm(prev,next)	switch_mm(prev, next, NULL)
 
+void verify_cpu_asid_bits(void);
+
 #endif
arch/arm64/kernel/cpufeature.c +12 -0
index 5ba3ef1..3614066 100644
@@ -24,6 +24,7 @@
 #include <asm/cpu.h>
 #include <asm/cpufeature.h>
 #include <asm/cpu_ops.h>
+#include <asm/mmu_context.h>
 #include <asm/processor.h>
 #include <asm/sysreg.h>
 
@@ -825,6 +826,15 @@ static u64 __raw_read_system_reg(u32 sys_id)
 }
 
 /*
+ * Check for CPU features that are used in early boot
+ * based on the Boot CPU value.
+ */
+static void check_early_cpu_features(void)
+{
+	verify_cpu_asid_bits();
+}
+
+/*
  * Run through the enabled system capabilities and enable() it on this CPU.
  * The capabilities were decided based on the available CPUs at the boot time.
  * Any new CPU should match the system wide status of the capability. If the
@@ -837,6 +847,8 @@ void verify_local_cpu_capabilities(void)
 	int i;
 	const struct arm64_cpu_capabilities *caps;
 
+	check_early_cpu_features();
+
 	/*
 	 * If we haven't computed the system capabilities, there is nothing
 	 * to verify.
arch/arm64/mm/context.c +15 -0
index 643bf4b..3e6786d 100644
@@ -24,6 +24,7 @@
 
 #include <asm/cpufeature.h>
 #include <asm/mmu_context.h>
+#include <asm/smp.h>
 #include <asm/tlbflush.h>
 
 static u32 asid_bits;
@@ -62,6 +63,20 @@ static u32 get_cpu_asid_bits(void)
 	return asid;
 }
 
+/* Check if the current cpu's ASIDBits is compatible with asid_bits */
+void verify_cpu_asid_bits(void)
+{
+	u32 asid = get_cpu_asid_bits();
+
+	if (asid < asid_bits) {
+		/* Differing ASIDBits is dangerous, crash the system */
+		pr_crit("CPU%d: smaller ASID size(%u) than boot CPU (%u)\n",
+				smp_processor_id(), asid, asid_bits);
+		update_cpu_boot_status(CPU_PANIC_KERNEL);
+		cpu_park_loop();
+	}
+}
+
 static void flush_context(unsigned int cpu)
 {
 	int i;
Signature · 1 line
1.7.9.5
#19 lore raw

v4 cover arm64: Verify early CPU features

In reply to Suzuki K Poulose
Quoted 9 lines · Suzuki K Poulose

From: Suzuki K. Poulose <[email protected]>

This series adds support for verifying some of the cpufeatures that are decided early in the boot process based on the boot CPU and cannot be delayed until all the CPUs are up (e.g, ASIDBits and may be VHE?). It also adds support for handling the failures in booting the secondary CPUs which could not be synchronised with the master CPU, otherwise.

Quoted 8 lines

It also adds one of the users of this early hook, check for ASIDBits. The mm_context id is based on the ASIDBits width supported by the boot CPU and is used early in the initialisation. So we need to make sure that all the secondary CPUs supports the width reported by the booting CPU, failing which we crash the system.

This series has been tested on Juno, Fast model by injecting smaller ASIDBits, lack of page-size support and missing features(PAN)

Module the comments from Mark and Catalin on patch 4, the rest looks good to me:

Will