From: Naman Jain <namjain@linux.microsoft.com>
To: Catalin Marinas <catalin.marinas@arm.com>, Will Deacon <will@kernel.org>
Cc: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, Marc Zyngier <maz@kernel.org>,
Thomas Huth <thuth@redhat.com>, Fuad Tabba <fuad.tabba@linux.dev>,
Jinjie Ruan <ruanjinjie@huawei.com>,
Thomas Gleixner <tglx@kernel.org>,
Pengjie Zhang <zhangpengjie2@huawei.com>,
mrigendrachaubey <mrigendra.chaubey@gmail.com>,
Saurabh Sengar <ssengar@linux.microsoft.com>
Subject: [PATCH] arm64: smp: distinguish secondary CPUs that hang after reaching head.S
Date: Wed, 22 Jul 2026 11:30:44 +0000 [thread overview]
Message-ID: <20260722113044.1835365-1-namjain@linux.microsoft.com> (raw)
When a secondary CPU fails to come online, __cpu_up() falls back to
__early_cpu_boot_status, but boot status 0x0 is ambiguous: it cannot
distinguish a CPU that never executed head.S (firmware/hypervisor never
dispatched it, so it never ran a single instruction) from one that
entered head.S, started executing, and then got stuck somewhere in kernel
bring-up. Add a change to let us tell those two cases apart, which
narrows down where to look when a CPU goes missing during boot.
Write a sentinel (CPU_BOOT_ASM_ENTERED, 0x10) as the first thing in
secondary_entry so the two cases can be told apart:
0x0 - CPU never ran head.S
0x10 - CPU entered head.S but did not come online
__cpu_up() resets the word before releasing each secondary, so a stale
value is not misattributed. Nothing clears the marker on the success
path. Reason being, the CPU instead sets CPU_BOOT_SUCCESS in
secondary_data.status, which __cpu_up() checks first, so
__early_cpu_boot_status is only read on failure. A stall before
CPU_BOOT_SUCCESS - including in secondary_start_kernel() - is therefore
reported as 0x10. The sentinel sits in the low byte and sets no
CPU_STUCK_REASON_* bits, so existing status decoding is unchanged.
Limitations: best-effort only. The status variable is global with no CPU
identity, and spin-table secondaries boot via secondary_holding_pen and
never write it, so 0x0 is a strong hint rather than a guarantee.
Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
---
arch/arm64/include/asm/smp.h | 10 ++++++++++
arch/arm64/kernel/head.S | 24 +++++++++++++++++++++++-
arch/arm64/kernel/smp.c | 21 +++++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 10ea4f5430690..ca330cd1b0615 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -20,6 +20,16 @@
/* Fatal system error detected by secondary CPU, crash the system */
#define CPU_PANIC_KERNEL (3)
+/*
+ * Best-effort marker written early in secondary_entry (PSCI/hotplug
+ * path) so __cpu_up() can tell whether a stuck CPU ever ran head.S.
+ * Not authoritative: spin-table secondaries use secondary_holding_pen
+ * and never write it, and the word has no CPU identity. Value must sit
+ * in the low byte, stay out of {0,1,2,3}, and set no CPU_STUCK_REASON_*
+ * bits.
+ */
+#define CPU_BOOT_ASM_ENTERED (0x10)
+
#define CPU_STUCK_REASON_52_BIT_VA (UL(1) << CPU_STUCK_REASON_SHIFT)
#define CPU_STUCK_REASON_NO_GRAN (UL(2) << CPU_STUCK_REASON_SHIFT)
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 87a822e5c4ca8..8ac3c49b4daf7 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -353,6 +353,22 @@ SYM_FUNC_END(secondary_holding_pen)
* be used where CPUs are brought online dynamically by the kernel.
*/
SYM_FUNC_START(secondary_entry)
+ /*
+ * Record that this CPU reached head.S, before init_kernel_el()
+ * sets SCTLR_ELx.EE. A single-byte store to the reader's low
+ * byte (__cpu_up() zeroed the word) is endian-agnostic, so a
+ * big-endian kernel still reads 0x10. Flush it like
+ * update_early_cpu_boot_status.
+ */
+ adr_l x2, __early_cpu_boot_status
+ mov w1, #CPU_BOOT_ASM_ENTERED
+#ifdef CONFIG_CPU_BIG_ENDIAN
+ strb w1, [x2, #7] // low byte at top address
+#else
+ strb w1, [x2]
+#endif
+ dmb sy
+ dc ivac, x2
mov x0, xzr
bl init_kernel_el // w0=cpu_boot_mode
b secondary_startup
@@ -386,7 +402,6 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
mov x0, x20
bl finalise_el2
- str_l xzr, __early_cpu_boot_status, x3
adr_l x5, vectors
msr vbar_el1, x5
isb
@@ -395,6 +410,13 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slow
+ /*
+ * Don't clear the marker here; nothing on the secondary clears
+ * it. Once the CPU sets CPU_BOOT_SUCCESS in
+ * secondary_data.status, __cpu_up() stops reading
+ * __early_cpu_boot_status (and resets it before the next
+ * release), so a stall before that still reports 0x10.
+ */
init_cpu_task x2, x1, x3
#ifdef CONFIG_ARM64_PTR_AUTH
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index cdcdd160e5b69..662205ae5a7a6 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -120,6 +120,17 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
secondary_data.task = idle;
update_cpu_boot_status(CPU_MMU_OFF);
+ /*
+ * Reset the marker before releasing this secondary: head.S
+ * never clears it, so without this a CPU that never reaches
+ * head.S would inherit a stale 0x10 from a previous CPU. Flush
+ * it so the MMU-off secondary sees the reset.
+ */
+ WRITE_ONCE(__early_cpu_boot_status, 0);
+ dcache_clean_inval_poc((unsigned long)&__early_cpu_boot_status,
+ (unsigned long)&__early_cpu_boot_status +
+ sizeof(__early_cpu_boot_status));
+
/* Now bring the CPU into our world */
ret = boot_secondary(cpu, idle);
if (ret) {
@@ -145,10 +156,20 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
switch (status & CPU_BOOT_STATUS_MASK) {
default:
+ /*
+ * 0x0 usually means the CPU never ran head.S, but
+ * treat it as a hint: spin-table secondaries never
+ * write the marker and the word has no CPU identity.
+ */
pr_err("CPU%u: failed in unknown state : 0x%lx\n",
cpu, status);
cpus_stuck_in_kernel++;
break;
+ case CPU_BOOT_ASM_ENTERED:
+ pr_err("CPU%u: entered head.S but not online : 0x%lx\n",
+ cpu, status);
+ cpus_stuck_in_kernel++;
+ break;
case CPU_KILL_ME:
if (!op_cpu_kill(cpu)) {
pr_crit("CPU%u: died during early boot\n", cpu);
base-commit: 290aaf24a551d5a0dce037e3fab30820f9113a10
--
2.43.0
reply other threads:[~2026-07-22 11:31 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260722113044.1835365-1-namjain@linux.microsoft.com \
--to=namjain@linux.microsoft.com \
--cc=catalin.marinas@arm.com \
--cc=fuad.tabba@linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maz@kernel.org \
--cc=mrigendra.chaubey@gmail.com \
--cc=ruanjinjie@huawei.com \
--cc=ssengar@linux.microsoft.com \
--cc=tglx@kernel.org \
--cc=thuth@redhat.com \
--cc=will@kernel.org \
--cc=zhangpengjie2@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox