* [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+
@ 2026-10-09 10:07 Will Deacon
2026-10-09 10:07 ` [PATCH v2 01/23] cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap() Will Deacon
` (22 more replies)
0 siblings, 23 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Hi everyone,
This is version two of the patches I previously posted here:
https://lore.kernel.org/all/20260907164024.17164-1-will@kernel.org/
There have been quite a few changes since last time, thanks to all the
feedback from humans and robots alike. These include:
* Added tags (but I didn't carry over Pankaj's T-b, since the series
has changed a fair bit).
* Fix secondary entry assembly code to handle timeouts correctly.
* Fix stashing of MPIDR so that we don't clobber STACK_END_MAGIC.
* Fix NULL dereference if PSCI fails to initialise.
* Enable RCU as soon as a secondary CPU is alive.
* Drop pointless (unreachable) optimisation to
arch_cpuhp_sync_state_poll().
* Include cpufeature patches from Fuad so that 'cpu_data' is populated
with register values taking into account any cmdline overrides. This
is necessary for correct handling of 'boot_cpu_data' later on, where
the previous version of this series stashed a stale copy of mpamidr.
As before, the first four patches are small changes to the generic code
to make it a little more "Arm-shaped" but the rest of the series is
largely confined to the arm64 architecture and PSCI driver code.
The series is based on -rc2, as it otherwise conflicts with Fuad's GMID
fix that was recently merged upstream.
Cheers,
Will
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Ștefania Ion <stefion@google.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Pankaj Patil <pankaj.patil@oss.qualcomm.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Tarun Sahu <tarunsahu@google.com>
Cc: Fuad Tabba <fuad.tabba@linux.dev>
Cc: David Woodhouse <dwmw@amazon.co.uk>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Marc Zyngier <maz@kernel.org>
--->8
Fuad Tabba (4):
arm64: cpufeature: Check arm64_ftr_regs[] before the first store
arm64: cpufeature: Add read_cpuid_with_overrides()
arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()
arm64: cpufeature: Store every ID register with its overrides applied
Will Deacon (19):
cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap()
cpu/hotplug: Avoid trying to bring up CPUs that are already online
cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op
cpu/hotplug: Propagate bring-up status to
arch_cpuhp_cleanup_kick_cpu()
arm64: smp: Tidy up smp_prepare_cpus()
arm64: smp: Tidy up cpuinfo init and cpufeature updates
arm64: smp: Defer update of secondary CPU capabilities
arm64: smp: Don't bother printing the I-cache policy for each CPU
arm64: smp: Defer RCU registration during secondary CPU bringup
arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU
onlining
arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP machinery for CPU
onlining
arm64: cpu_ops: Make 'cpu_operations' pointer global instead of
per-cpu
arm64: cpu_ops: Introduce get_secondary_cpu_ops()
firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs
firmware/psci: Extend ->cpu_on() callback to take an additional
argument
arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot()
arm64: smp: Pass secondary CPU boot parameters via firmware if
possible
arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining
arm64: smp: Harden parallel CPU bringup against broken PSCI firmware
arch/arm/kernel/psci_smp.c | 4 +-
arch/arm64/Kconfig | 2 +-
arch/arm64/include/asm/cpu.h | 7 +-
arch/arm64/include/asm/cpu_ops.h | 7 +-
arch/arm64/include/asm/cpufeature.h | 11 +
arch/arm64/include/asm/smp.h | 40 ++-
arch/arm64/include/asm/topology.h | 2 +
arch/arm64/kernel/acpi_parking_protocol.c | 3 +-
arch/arm64/kernel/asm-offsets.c | 1 +
arch/arm64/kernel/cpu_ops.c | 34 +-
arch/arm64/kernel/cpufeature.c | 397 +++++++++++-----------
arch/arm64/kernel/cpuinfo.c | 122 +++----
arch/arm64/kernel/head.S | 60 +++-
arch/arm64/kernel/psci.c | 14 +-
arch/arm64/kernel/smp.c | 183 +++++-----
arch/arm64/kernel/smp_spin_table.c | 2 +-
arch/arm64/mm/mmu.c | 2 +-
arch/x86/kernel/smpboot.c | 4 +-
drivers/firmware/psci/psci.c | 27 +-
include/linux/cpuhotplug.h | 2 +-
include/linux/psci.h | 3 +-
include/linux/rcutree.h | 2 +-
kernel/cpu.c | 18 +-
23 files changed, 508 insertions(+), 439 deletions(-)
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 01/23] cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 02/23] cpu/hotplug: Avoid trying to bring up CPUs that are already online Will Deacon
` (21 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
cpuhp_can_boot_ap() uses atomic_try_cmpxchg() to transition the sync
state of the incoming CPU to SYNC_STATE_KICKED. However, this is
unnecessary if the state is SYNC_STATE_DEAD, since there will not be any
concurrent state modifications, and also if the state is already set to
SYNC_STATE_KICKED.
Restrict the use of cmpxchg() to the case where the existing state is
SYNC_STATE_ALIVE.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
kernel/cpu.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/kernel/cpu.c b/kernel/cpu.c
index b3c8553d7bd6..198c929c452a 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -408,22 +408,22 @@ static bool cpuhp_can_boot_ap(unsigned int cpu)
switch (sync) {
case SYNC_STATE_DEAD:
/* CPU is properly dead */
+ atomic_set(st, SYNC_STATE_KICKED);
break;
case SYNC_STATE_KICKED:
/* CPU did not come up in previous attempt */
break;
case SYNC_STATE_ALIVE:
/* CPU is stuck cpuhp_ap_sync_alive(). */
+ if (!atomic_try_cmpxchg_relaxed(st, &sync, SYNC_STATE_KICKED))
+ goto again;
break;
default:
/* CPU failed to report online or dead and is in limbo state. */
return false;
}
- /* Prepare for booting */
- if (!atomic_try_cmpxchg(st, &sync, SYNC_STATE_KICKED))
- goto again;
-
+ /* Continue with booting */
return true;
}
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 02/23] cpu/hotplug: Avoid trying to bring up CPUs that are already online
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
2026-10-09 10:07 ` [PATCH v2 01/23] cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op Will Deacon
` (20 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
There's little point trying to bring up a CPU that is already online.
Although _cpu_up() handles this case by doing nothing (because the
target state has already been reached), it's wasted effort when we can
easily elide the call to cpu_up() in the first place.
Check that the target CPU isn't already online before invoking cpu_up()
from cpuhp_bringup_mask().
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
kernel/cpu.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/cpu.c b/kernel/cpu.c
index 198c929c452a..97a9bfe4edad 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -1769,7 +1769,8 @@ static void __init cpuhp_bringup_mask(const struct cpumask *mask, unsigned int n
for_each_cpu(cpu, mask) {
struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);
- if (cpu_up(cpu, target) && can_rollback_cpu(st)) {
+ if (!cpu_online(cpu) && cpu_up(cpu, target) &&
+ can_rollback_cpu(st)) {
/*
* If this failed then cpu_up() might have only
* rolled back to CPUHP_BP_KICK_AP for the final
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
2026-10-09 10:07 ` [PATCH v2 01/23] cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap() Will Deacon
2026-10-09 10:07 ` [PATCH v2 02/23] cpu/hotplug: Avoid trying to bring up CPUs that are already online Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:46 ` David Woodhouse
2026-10-09 10:07 ` [PATCH v2 04/23] cpu/hotplug: Propagate bring-up status to arch_cpuhp_cleanup_kick_cpu() Will Deacon
` (19 subsequent siblings)
22 siblings, 1 reply; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
On some architectures (such as arm64), cpu_relax() is effectively a NOP
and so isn't particularly efficient when used in a tight polling loop
such as the CPU state synchronisation in cpuhp_ap_sync_alive().
Once an incoming CPU has reached the SYNC_STATE_ALIVE state, we know
that it is executing within the kernel and so we can use the more
efficient polling mechanism provided by the atomic_cond_read* API,
allowing the use of WFE on arm64 and Zawrs on RISC-V.
Change cpuhp_ap_sync_alive() to poll using atomic_cond_read_relaxed()
instead of cpu_relax(). No change on x86.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
kernel/cpu.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/kernel/cpu.c b/kernel/cpu.c
index 97a9bfe4edad..786c3191431c 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -395,8 +395,7 @@ void cpuhp_ap_sync_alive(void)
cpuhp_ap_update_sync_state(SYNC_STATE_ALIVE);
/* Wait for the control CPU to release it. */
- while (atomic_read(st) != SYNC_STATE_SHOULD_ONLINE)
- cpu_relax();
+ atomic_cond_read_acquire(st, VAL == SYNC_STATE_SHOULD_ONLINE);
}
static bool cpuhp_can_boot_ap(unsigned int cpu)
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 04/23] cpu/hotplug: Propagate bring-up status to arch_cpuhp_cleanup_kick_cpu()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (2 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store Will Deacon
` (18 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
In preparation for enabling the generic CPU hotplug machinery on arm64,
which has architecture-specific handling of early bringup failures,
extend arch_cpuhp_cleanup_kick_cpu() to take an additional argument
indicating whether or not the target AP reached the alive state.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/x86/kernel/smpboot.c | 4 ++--
include/linux/cpuhotplug.h | 2 +-
kernel/cpu.c | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c
index ba01a9e919b7..f10c64504244 100644
--- a/arch/x86/kernel/smpboot.c
+++ b/arch/x86/kernel/smpboot.c
@@ -1074,7 +1074,7 @@ static int do_boot_cpu(u32 apicid, unsigned int cpu, struct task_struct *idle)
/* If the wakeup mechanism failed, cleanup the warm reset vector */
if (ret)
- arch_cpuhp_cleanup_kick_cpu(cpu);
+ arch_cpuhp_cleanup_kick_cpu(cpu, false);
return ret;
}
@@ -1122,7 +1122,7 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *tidle)
return smp_ops.kick_ap_alive(cpu, tidle);
}
-void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu)
+void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
{
/* Cleanup possible dangling ends... */
if (smp_ops.kick_ap_alive == native_kick_ap && x86_platform.legacy.warm_reset)
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index feb32949aeea..cbdcf1659d90 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -510,7 +510,7 @@ struct task_struct;
void cpuhp_ap_sync_alive(void);
void arch_cpuhp_sync_state_poll(void);
-void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu);
+void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive);
int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *tidle);
bool arch_cpuhp_init_parallel_bringup(void);
diff --git a/kernel/cpu.c b/kernel/cpu.c
index 786c3191431c..df3b6bc4c86b 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -426,7 +426,7 @@ static bool cpuhp_can_boot_ap(unsigned int cpu)
return true;
}
-void __weak arch_cpuhp_cleanup_kick_cpu(unsigned int cpu) { }
+void __weak arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive) { }
/*
* Early CPU bringup synchronization point. Cannot use cpuhp_state::done_up
@@ -445,7 +445,7 @@ static int cpuhp_bp_sync_alive(unsigned int cpu)
}
/* Let the architecture cleanup the kick alive mechanics. */
- arch_cpuhp_cleanup_kick_cpu(cpu);
+ arch_cpuhp_cleanup_kick_cpu(cpu, !ret);
return ret;
}
#else /* CONFIG_HOTPLUG_CORE_SYNC_FULL */
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (3 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 04/23] cpu/hotplug: Propagate bring-up status to arch_cpuhp_cleanup_kick_cpu() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 06/23] arm64: cpufeature: Add read_cpuid_with_overrides() Will Deacon
` (17 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
From: Fuad Tabba <fuad.tabba@linux.dev>
sort_ftr_regs() sorts nothing. It checks that arm64_ftr_regs[] is in
sys_id order, which the binary search in get_arm64_ftr_reg() relies on,
and that no register's fields overlap. It runs at the top of
init_cpu_features(), after the boot CPU's __cpuinfo_store_cpu(). A
subsequent patch makes __cpuinfo_store_cpu() look registers up in the
table.
Rename it validate_ftr_regs() and call it from smp_prepare_boot_cpu(),
ahead of cpuinfo_store_boot_cpu().
Suggested-by: Will Deacon <will@kernel.org>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpu.h | 1 +
arch/arm64/kernel/cpufeature.c | 5 +----
arch/arm64/kernel/smp.c | 2 ++
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h
index 3c008821219c..f332fbd8e46d 100644
--- a/arch/arm64/include/asm/cpu.h
+++ b/arch/arm64/include/asm/cpu.h
@@ -75,6 +75,7 @@ DECLARE_PER_CPU(struct cpuinfo_arm64, cpu_data);
void cpuinfo_store_cpu(void);
void __init cpuinfo_store_boot_cpu(void);
+void __init validate_ftr_regs(void);
void __init init_cpu_features(struct cpuinfo_arm64 *info);
void update_cpu_features(int cpu, struct cpuinfo_arm64 *info,
struct cpuinfo_arm64 *boot);
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 32102c3912fa..727e1665c987 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -962,7 +962,7 @@ s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new,
return ret;
}
-static void __init sort_ftr_regs(void)
+void __init validate_ftr_regs(void)
{
unsigned int i;
@@ -1207,9 +1207,6 @@ bool gmid_el1_accessible(const struct cpuinfo_arm64 *info)
void __init init_cpu_features(struct cpuinfo_arm64 *info)
{
- /* Before we start using the tables, make sure it is sorted */
- sort_ftr_regs();
-
init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr);
init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid);
init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq);
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index a61dc3016a11..eac217b9ebf1 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -455,6 +455,8 @@ void __init smp_prepare_boot_cpu(void)
*/
set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
+ /* Check arm64_ftr_regs[] before anything looks registers up in it. */
+ validate_ftr_regs();
cpuinfo_store_boot_cpu();
setup_boot_cpu_features();
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 06/23] arm64: cpufeature: Add read_cpuid_with_overrides()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (4 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu() Will Deacon
` (16 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier, Suzuki K Poulose
From: Fuad Tabba <fuad.tabba@linux.dev>
__cpuinfo_store_cpu() stores the ID registers raw. Its gates on reading
a feature's register test those raw values. The exception is
gmid_el1_accessible(), which open-codes MTE's command-line override. It
checks the override with arm64_ftr_safe_value(), since
init_cpu_features() hasn't yet removed an unsafe one. The other gates
still read a feature's register when the command line disabled it. That's
the read that trapped on GMID_EL1.
Add read_cpuid_with_overrides(), which applies every valid override to
any register in arm64_ftr_regs[]. An override is valid when
arm64_ftr_safe_value() returns it unchanged. validate_ftr_regs() has
checked the table by the first store, so the lookup is safe there.
arm64_apply_feature_override() can't do this: it takes one field and no
descriptor to check the override against.
Store ID_AA64PFR1_EL1 through it, and replace gmid_el1_accessible()
with a test on the stored value at its three callers, as for SME.
Other readers of the stored value see the override too. With
arm64.nosme, __cpuinfo_store_cpu() no longer reads SMIDR_EL1. CPUs that
differ only in an ID_AA64PFR1_EL1 field the command line turned off no
longer taint the kernel. init_cpu_ftr_reg() logs a valid override as
"already set" rather than "forced".
The helper is Suzuki's override_cpu_ftr_reg(), without its other two
call sites.
Co-developed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Suggested-by: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/all/afc5bd00-28ca-413b-b047-ee53589c285d@arm.com/
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpu.h | 1 -
arch/arm64/include/asm/cpufeature.h | 11 ++++++
arch/arm64/kernel/cpufeature.c | 57 ++++++++++++++---------------
arch/arm64/kernel/cpuinfo.c | 4 +-
4 files changed, 41 insertions(+), 32 deletions(-)
diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h
index f332fbd8e46d..6d2a5ac4c2f8 100644
--- a/arch/arm64/include/asm/cpu.h
+++ b/arch/arm64/include/asm/cpu.h
@@ -79,6 +79,5 @@ void __init validate_ftr_regs(void);
void __init init_cpu_features(struct cpuinfo_arm64 *info);
void update_cpu_features(int cpu, struct cpuinfo_arm64 *info,
struct cpuinfo_arm64 *boot);
-bool gmid_el1_accessible(const struct cpuinfo_arm64 *info);
#endif /* __ASM_CPU_H */
diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index 4f04ad82ea34..5a4d25325856 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -627,6 +627,13 @@ static inline bool id_aa64pfr1_mpamfrac(u64 pfr1)
return val > 0;
}
+static inline bool id_aa64pfr1_mte(u64 pfr1)
+{
+ u32 val = cpuid_feature_extract_unsigned_field(pfr1, ID_AA64PFR1_EL1_MTE_SHIFT);
+
+ return val >= ID_AA64PFR1_EL1_MTE_MTE2;
+}
+
void __init setup_boot_cpu_features(void);
void __init setup_system_features(void);
void __init setup_user_features(void);
@@ -957,6 +964,10 @@ static inline unsigned int get_vmid_bits(u64 mmfr1)
s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur);
struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id);
+u64 arm64_ftr_reg_override(u32 sys_id, u64 val);
+
+/* Read this CPU's ID register with each valid command-line override applied. */
+#define read_cpuid_with_overrides(reg) arm64_ftr_reg_override(SYS_##reg, read_cpuid(reg))
extern struct arm64_ftr_override id_aa64mmfr0_override;
extern struct arm64_ftr_override id_aa64mmfr1_override;
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 727e1665c987..3161531ad401 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -962,6 +962,32 @@ s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new,
return ret;
}
+u64 arm64_ftr_reg_override(u32 sys_id, u64 val)
+{
+ struct arm64_ftr_reg *reg = get_arm64_ftr_reg(sys_id);
+ const struct arm64_ftr_bits *ftrp;
+
+ if (!reg || !reg->override->mask)
+ return val;
+
+ for (ftrp = reg->ftr_bits; ftrp->width; ftrp++) {
+ u64 ftr_mask = arm64_ftr_mask(ftrp);
+ s64 ftr_new, ftr_ovr;
+
+ if ((ftr_mask & reg->override->mask) != ftr_mask)
+ continue;
+
+ ftr_new = arm64_ftr_value(ftrp, val);
+ ftr_ovr = arm64_ftr_value(ftrp, reg->override->val);
+
+ /* An unsafe override is left for init_cpu_ftr_reg() to remove. */
+ if (arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new) == ftr_ovr)
+ val = arm64_ftr_set_value(ftrp, val, ftr_ovr);
+ }
+
+ return val;
+}
+
void __init validate_ftr_regs(void)
{
unsigned int i;
@@ -1178,33 +1204,6 @@ static bool detect_ftr_has_mpam(void)
return id_aa64pfr0_mpam(pfr0) || id_aa64pfr1_mpamfrac(pfr1);
}
-bool gmid_el1_accessible(const struct cpuinfo_arm64 *info)
-{
- const struct arm64_ftr_bits *ftrp;
- s64 mte, ovr;
- u64 ftr_mask;
-
- /* No ID register reflects CONFIG_ARM64_MTE. */
- if (!IS_ENABLED(CONFIG_ARM64_MTE))
- return false;
-
- for (ftrp = ftr_id_aa64pfr1; ftrp->width; ftrp++) {
- if (ftrp->shift == ID_AA64PFR1_EL1_MTE_SHIFT)
- break;
- }
-
- ftr_mask = arm64_ftr_mask(ftrp);
- mte = arm64_ftr_value(ftrp, info->reg_id_aa64pfr1);
-
- /* The boot CPU runs before init_cpu_ftr_reg() strips unsafe overrides. */
- if ((id_aa64pfr1_override.mask & ftr_mask) == ftr_mask) {
- ovr = arm64_ftr_value(ftrp, id_aa64pfr1_override.val);
- mte = arm64_ftr_safe_value(ftrp, ovr, mte);
- }
-
- return mte >= ID_AA64PFR1_EL1_MTE_MTE2;
-}
-
void __init init_cpu_features(struct cpuinfo_arm64 *info)
{
init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr);
@@ -1254,7 +1253,7 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info)
init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr);
}
- if (gmid_el1_accessible(info))
+ if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid);
}
@@ -1516,7 +1515,7 @@ void update_cpu_features(int cpu,
* they read/write depends on the GMID_EL1.BS field. Check that the
* value is the same on all CPUs.
*/
- if (gmid_el1_accessible(info))
+ if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu,
info->reg_gmid, boot->reg_gmid);
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index 45c63f3d75c5..d48167fe4218 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -496,13 +496,13 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1);
info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1);
info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1);
- info->reg_id_aa64pfr1 = read_cpuid(ID_AA64PFR1_EL1);
+ info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1);
info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1);
info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1);
info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1);
- if (gmid_el1_accessible(info))
+ if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
info->reg_gmid = read_cpuid(GMID_EL1);
if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (5 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 06/23] arm64: cpufeature: Add read_cpuid_with_overrides() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 15:44 ` Ben Horgan
2026-10-09 10:07 ` [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied Will Deacon
` (15 subsequent siblings)
22 siblings, 1 reply; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
From: Fuad Tabba <fuad.tabba@linux.dev>
MPAMIDR_EL1 is the one ID register __cpuinfo_store_cpu() doesn't read.
Its read was deferred to init_cpu_features() and update_cpu_features()
because it traps on firmware that fails to enable MPAM. Only the
sanitised ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 carried the arm64.nompam
override for such firmware.
Store ID_AA64PFR0_EL1 through read_cpuid_with_overrides() as well. Then
read MPAMIDR_EL1 in __cpuinfo_store_cpu() again, beside GMID_EL1, gated
on this CPU's overridden ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1. cpu_data
is then complete before init_cpu_features() runs.
Other readers of the stored ID_AA64PFR0_EL1 see the override too. With
arm64.no32bit_el0, __cpuinfo_store_cpu() no longer reads the AArch32 ID
registers. Adding allow_mismatched_32bit_el0 no longer enables 32-bit
EL0 either.
detect_ftr_has_mpam(), a system-wide test on the sanitised values,
stays for the ARM64_MPAM capability.
Suggested-by: Will Deacon <will@kernel.org>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/cpufeature.c | 10 +++-------
arch/arm64/kernel/cpuinfo.c | 11 ++++-------
2 files changed, 7 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 3161531ad401..9212f10c1d1a 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -1248,10 +1248,8 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info)
cpacr_restore(cpacr);
}
- if (detect_ftr_has_mpam()) {
- info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
+ if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr);
- }
if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid);
@@ -1504,11 +1502,9 @@ void update_cpu_features(int cpu,
cpacr_restore(cpacr);
}
- if (detect_ftr_has_mpam()) {
- info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
+ if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu,
- info->reg_mpamidr, boot->reg_mpamidr);
- }
+ info->reg_mpamidr, boot->reg_mpamidr);
/*
* The kernel uses the LDGM/STGM instructions and the number of tags
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index d48167fe4218..0ae40b0c7b2f 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -495,7 +495,7 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1);
info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1);
- info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1);
+ info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1);
info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1);
info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1);
info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
@@ -505,15 +505,12 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
info->reg_gmid = read_cpuid(GMID_EL1);
+ if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
+ info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
+
if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
__cpuinfo_store_cpu_32bit(&info->aarch32);
- /*
- * info->reg_mpamidr deferred to {init,update}_cpu_features because we
- * don't want to read it (and trigger a trap on buggy firmware) if
- * using an aa64pfr0_el1 override to unconditionally disable MPAM.
- */
-
if (IS_ENABLED(CONFIG_ARM64_SME) &&
id_aa64pfr1_sme(info->reg_id_aa64pfr1)) {
/*
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (6 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 15:51 ` Ben Horgan
2026-10-09 10:07 ` [PATCH v2 09/23] arm64: smp: Tidy up smp_prepare_cpus() Will Deacon
` (14 subsequent siblings)
22 siblings, 1 reply; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
From: Fuad Tabba <fuad.tabba@linux.dev>
__cpuinfo_store_cpu() stores ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 with
their overrides applied and every other register raw. Store the rest
the same way. Everything read with read_cpuid() goes through
read_cpuid_with_overrides(). CTR_EL0 and CNTFRQ_EL0, which are read
otherwise, go through arm64_ftr_reg_override(). It returns a register
with no override unchanged, so an override added to one later takes
effect without a change here.
init_cpu_ftr_reg() then gets values that already carry every valid
override, so its code to apply one is dead. Remove that code.
init_cpu_ftr_reg() still removes an unsafe override, so the same
overrides are rejected and sys_val is unchanged. Only its log changes.
The raw value is gone, so it logs an override equal to that value as
"forced" rather than "already set".
Secondaries are compared with the boot CPU on their stored values, so
CPUs that differ only in a field the command line turned off no longer
taint the kernel.
Suggested-by: Will Deacon <will@kernel.org>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/cpufeature.c | 17 +++----
arch/arm64/kernel/cpuinfo.c | 82 +++++++++++++++++-----------------
2 files changed, 46 insertions(+), 53 deletions(-)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 9212f10c1d1a..d69a5b8304b6 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -1043,6 +1043,8 @@ void __init validate_ftr_regs(void)
* Also initialises the strict_mask for the register.
* Any bits that are not covered by an arm64_ftr_bits entry are considered
* RES0 for the system-wide value, and must strictly match.
+ * @new has been through arm64_ftr_reg_override(), so a valid override is
+ * already applied to it. Only an unsafe one is left to remove here.
*/
static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
{
@@ -1063,29 +1065,20 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
s64 ftr_ovr = arm64_ftr_value(ftrp, reg->override->val);
if ((ftr_mask & reg->override->mask) == ftr_mask) {
- s64 tmp = arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new);
- char *str = NULL;
+ char *str = "forced";
- if (ftr_ovr != tmp) {
+ if (arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new) != ftr_ovr) {
/* Unsafe, remove the override */
reg->override->mask &= ~ftr_mask;
reg->override->val &= ~ftr_mask;
- tmp = ftr_ovr;
str = "ignoring override";
- } else if (ftr_new != tmp) {
- /* Override was valid */
- ftr_new = tmp;
- str = "forced";
- } else {
- /* Override was the safe value */
- str = "already set";
}
pr_warn("%s[%d:%d]: %s to %llx\n",
reg->name,
ftrp->shift + ftrp->width - 1,
ftrp->shift, str,
- tmp & (BIT(ftrp->width) - 1));
+ ftr_ovr & (BIT(ftrp->width) - 1));
} else if ((ftr_mask & reg->override->val) == ftr_mask) {
reg->override->val &= ~ftr_mask;
pr_warn("%s[%d:%d]: impossible override, ignored\n",
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index 0ae40b0c7b2f..1b92e52989b1 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -443,33 +443,33 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
static void __cpuinfo_store_cpu_32bit(struct cpuinfo_32bit *info)
{
- info->reg_id_dfr0 = read_cpuid(ID_DFR0_EL1);
- info->reg_id_dfr1 = read_cpuid(ID_DFR1_EL1);
- info->reg_id_isar0 = read_cpuid(ID_ISAR0_EL1);
- info->reg_id_isar1 = read_cpuid(ID_ISAR1_EL1);
- info->reg_id_isar2 = read_cpuid(ID_ISAR2_EL1);
- info->reg_id_isar3 = read_cpuid(ID_ISAR3_EL1);
- info->reg_id_isar4 = read_cpuid(ID_ISAR4_EL1);
- info->reg_id_isar5 = read_cpuid(ID_ISAR5_EL1);
- info->reg_id_isar6 = read_cpuid(ID_ISAR6_EL1);
- info->reg_id_mmfr0 = read_cpuid(ID_MMFR0_EL1);
- info->reg_id_mmfr1 = read_cpuid(ID_MMFR1_EL1);
- info->reg_id_mmfr2 = read_cpuid(ID_MMFR2_EL1);
- info->reg_id_mmfr3 = read_cpuid(ID_MMFR3_EL1);
- info->reg_id_mmfr4 = read_cpuid(ID_MMFR4_EL1);
- info->reg_id_mmfr5 = read_cpuid(ID_MMFR5_EL1);
- info->reg_id_pfr0 = read_cpuid(ID_PFR0_EL1);
- info->reg_id_pfr1 = read_cpuid(ID_PFR1_EL1);
- info->reg_id_pfr2 = read_cpuid(ID_PFR2_EL1);
+ info->reg_id_dfr0 = read_cpuid_with_overrides(ID_DFR0_EL1);
+ info->reg_id_dfr1 = read_cpuid_with_overrides(ID_DFR1_EL1);
+ info->reg_id_isar0 = read_cpuid_with_overrides(ID_ISAR0_EL1);
+ info->reg_id_isar1 = read_cpuid_with_overrides(ID_ISAR1_EL1);
+ info->reg_id_isar2 = read_cpuid_with_overrides(ID_ISAR2_EL1);
+ info->reg_id_isar3 = read_cpuid_with_overrides(ID_ISAR3_EL1);
+ info->reg_id_isar4 = read_cpuid_with_overrides(ID_ISAR4_EL1);
+ info->reg_id_isar5 = read_cpuid_with_overrides(ID_ISAR5_EL1);
+ info->reg_id_isar6 = read_cpuid_with_overrides(ID_ISAR6_EL1);
+ info->reg_id_mmfr0 = read_cpuid_with_overrides(ID_MMFR0_EL1);
+ info->reg_id_mmfr1 = read_cpuid_with_overrides(ID_MMFR1_EL1);
+ info->reg_id_mmfr2 = read_cpuid_with_overrides(ID_MMFR2_EL1);
+ info->reg_id_mmfr3 = read_cpuid_with_overrides(ID_MMFR3_EL1);
+ info->reg_id_mmfr4 = read_cpuid_with_overrides(ID_MMFR4_EL1);
+ info->reg_id_mmfr5 = read_cpuid_with_overrides(ID_MMFR5_EL1);
+ info->reg_id_pfr0 = read_cpuid_with_overrides(ID_PFR0_EL1);
+ info->reg_id_pfr1 = read_cpuid_with_overrides(ID_PFR1_EL1);
+ info->reg_id_pfr2 = read_cpuid_with_overrides(ID_PFR2_EL1);
- info->reg_mvfr0 = read_cpuid(MVFR0_EL1);
- info->reg_mvfr1 = read_cpuid(MVFR1_EL1);
- info->reg_mvfr2 = read_cpuid(MVFR2_EL1);
+ info->reg_mvfr0 = read_cpuid_with_overrides(MVFR0_EL1);
+ info->reg_mvfr1 = read_cpuid_with_overrides(MVFR1_EL1);
+ info->reg_mvfr2 = read_cpuid_with_overrides(MVFR2_EL1);
}
static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
{
- info->reg_cntfrq = arch_timer_get_cntfrq();
+ info->reg_cntfrq = arm64_ftr_reg_override(SYS_CNTFRQ_EL0, arch_timer_get_cntfrq());
/*
* Use the effective value of the CTR_EL0 than the raw value
* exposed by the CPU. CTR_EL0.IDC field value must be interpreted
@@ -478,35 +478,35 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
* effective value of the CTR_EL0 in our internal records for
* accurate sanity check and feature enablement.
*/
- info->reg_ctr = read_cpuid_effective_cachetype();
- info->reg_dczid = read_cpuid(DCZID_EL0);
+ info->reg_ctr = arm64_ftr_reg_override(SYS_CTR_EL0, read_cpuid_effective_cachetype());
+ info->reg_dczid = read_cpuid_with_overrides(DCZID_EL0);
info->reg_midr = read_cpuid_id();
info->reg_revidr = read_cpuid(REVIDR_EL1);
info->reg_aidr = read_cpuid(AIDR_EL1);
- info->reg_id_aa64dfr0 = read_cpuid(ID_AA64DFR0_EL1);
- info->reg_id_aa64dfr1 = read_cpuid(ID_AA64DFR1_EL1);
- info->reg_id_aa64isar0 = read_cpuid(ID_AA64ISAR0_EL1);
- info->reg_id_aa64isar1 = read_cpuid(ID_AA64ISAR1_EL1);
- info->reg_id_aa64isar2 = read_cpuid(ID_AA64ISAR2_EL1);
- info->reg_id_aa64isar3 = read_cpuid(ID_AA64ISAR3_EL1);
- info->reg_id_aa64mmfr0 = read_cpuid(ID_AA64MMFR0_EL1);
- info->reg_id_aa64mmfr1 = read_cpuid(ID_AA64MMFR1_EL1);
- info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
- info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1);
- info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1);
+ info->reg_id_aa64dfr0 = read_cpuid_with_overrides(ID_AA64DFR0_EL1);
+ info->reg_id_aa64dfr1 = read_cpuid_with_overrides(ID_AA64DFR1_EL1);
+ info->reg_id_aa64isar0 = read_cpuid_with_overrides(ID_AA64ISAR0_EL1);
+ info->reg_id_aa64isar1 = read_cpuid_with_overrides(ID_AA64ISAR1_EL1);
+ info->reg_id_aa64isar2 = read_cpuid_with_overrides(ID_AA64ISAR2_EL1);
+ info->reg_id_aa64isar3 = read_cpuid_with_overrides(ID_AA64ISAR3_EL1);
+ info->reg_id_aa64mmfr0 = read_cpuid_with_overrides(ID_AA64MMFR0_EL1);
+ info->reg_id_aa64mmfr1 = read_cpuid_with_overrides(ID_AA64MMFR1_EL1);
+ info->reg_id_aa64mmfr2 = read_cpuid_with_overrides(ID_AA64MMFR2_EL1);
+ info->reg_id_aa64mmfr3 = read_cpuid_with_overrides(ID_AA64MMFR3_EL1);
+ info->reg_id_aa64mmfr4 = read_cpuid_with_overrides(ID_AA64MMFR4_EL1);
info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1);
info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1);
- info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1);
- info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
- info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1);
- info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1);
+ info->reg_id_aa64pfr2 = read_cpuid_with_overrides(ID_AA64PFR2_EL1);
+ info->reg_id_aa64zfr0 = read_cpuid_with_overrides(ID_AA64ZFR0_EL1);
+ info->reg_id_aa64smfr0 = read_cpuid_with_overrides(ID_AA64SMFR0_EL1);
+ info->reg_id_aa64fpfr0 = read_cpuid_with_overrides(ID_AA64FPFR0_EL1);
if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
- info->reg_gmid = read_cpuid(GMID_EL1);
+ info->reg_gmid = read_cpuid_with_overrides(GMID_EL1);
if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
- info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
+ info->reg_mpamidr = read_cpuid_with_overrides(MPAMIDR_EL1);
if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
__cpuinfo_store_cpu_32bit(&info->aarch32);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 09/23] arm64: smp: Tidy up smp_prepare_cpus()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (7 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 10/23] arm64: smp: Tidy up cpuinfo init and cpufeature updates Will Deacon
` (13 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
smp_prepare_cpus() is always run on the boot CPU (i.e. CPU 0) but goes
to great lengths to support running on a CPU where smp_processor_id()
is non-zero.
Clean up the code a little by hardcoding zero for the boot CPU ID.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/smp.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index eac217b9ebf1..d2aa949e33a3 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -781,16 +781,14 @@ void __init smp_init_cpus(void)
void __init smp_prepare_cpus(unsigned int max_cpus)
{
const struct cpu_operations *ops;
- int err;
unsigned int cpu;
- unsigned int this_cpu;
+ int err;
init_cpu_topology();
- this_cpu = smp_processor_id();
- store_cpu_topology(this_cpu);
- numa_store_cpu_info(this_cpu);
- numa_add_cpu(this_cpu);
+ store_cpu_topology(0);
+ numa_store_cpu_info(0);
+ numa_add_cpu(0);
/*
* If UP is mandated by "nosmp" (which implies "maxcpus=0"), don't set
@@ -805,8 +803,7 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
* secondaries from the bootloader.
*/
for_each_possible_cpu(cpu) {
-
- if (cpu == smp_processor_id())
+ if (cpu == 0)
continue;
ops = get_cpu_ops(cpu);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 10/23] arm64: smp: Tidy up cpuinfo init and cpufeature updates
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (8 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 09/23] arm64: smp: Tidy up smp_prepare_cpus() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 11/23] arm64: smp: Defer update of secondary CPU capabilities Will Deacon
` (12 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Populating the 'cpuinfo_arm64' structure during CPU bringup and
subsequently checking/updating cpufeature structures is slightly
convoluted and differs unnecessarily between the boot CPU and secondary
CPUs.
Rework the code so that cpuinfo_store_cpu() is used to populate the
'cpuinfo_arm64' structure for each CPU, with secondary CPUs then calling
update_cpu_features() to update the global view of the available
features. This allows us to internalise init_cpu_features() and the
'boot_cpu_data' in cpufeature.c and paves the way for parallelising the
ID register probing during bring-up of secondary CPUs.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpu.h | 5 +----
arch/arm64/kernel/cpufeature.c | 21 +++++++++++++++++----
arch/arm64/kernel/cpuinfo.c | 11 -----------
arch/arm64/kernel/smp.c | 3 ++-
4 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h
index 6d2a5ac4c2f8..3d1693aaf4e0 100644
--- a/arch/arm64/include/asm/cpu.h
+++ b/arch/arm64/include/asm/cpu.h
@@ -73,11 +73,8 @@ struct cpuinfo_arm64 {
DECLARE_PER_CPU(struct cpuinfo_arm64, cpu_data);
void cpuinfo_store_cpu(void);
-void __init cpuinfo_store_boot_cpu(void);
+void update_cpu_features(int cpu);
void __init validate_ftr_regs(void);
-void __init init_cpu_features(struct cpuinfo_arm64 *info);
-void update_cpu_features(int cpu, struct cpuinfo_arm64 *info,
- struct cpuinfo_arm64 *boot);
#endif /* __ASM_CPU_H */
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index d69a5b8304b6..5db456884ffa 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -117,6 +117,7 @@ EXPORT_SYMBOL(system_cpucaps);
static struct arm64_cpu_capabilities const __ro_after_init *cpucap_ptrs[ARM64_NCAPS];
DECLARE_BITMAP(boot_cpucaps, ARM64_NCAPS);
+static struct cpuinfo_arm64 boot_cpu_data;
/*
* arm64_use_ng_mappings must be placed in the .data section, otherwise it
@@ -1197,8 +1198,16 @@ static bool detect_ftr_has_mpam(void)
return id_aa64pfr0_mpam(pfr0) || id_aa64pfr1_mpamfrac(pfr1);
}
-void __init init_cpu_features(struct cpuinfo_arm64 *info)
+static void __init init_cpu_features(void)
{
+ struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0);
+
+ /*
+ * We keep a copy of the boot CPU registers so that physical hotplug
+ * of CPU 0 can still be properly checked.
+ */
+ boot_cpu_data = *info;
+
init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr);
init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid);
init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq);
@@ -1391,12 +1400,14 @@ static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info,
* non-boot CPU. Also performs SANITY checks to make sure that there
* aren't any insane variations from that of the boot CPU.
*/
-void update_cpu_features(int cpu,
- struct cpuinfo_arm64 *info,
- struct cpuinfo_arm64 *boot)
+void update_cpu_features(int cpu)
{
+ struct cpuinfo_arm64 *boot, *info;
int taint = 0;
+ boot = &boot_cpu_data;
+ info = per_cpu_ptr(&cpu_data, cpu);
+
/*
* The kernel can handle differing I-cache policies, but otherwise
* caches should look identical. Userspace JITs will make use of
@@ -3963,6 +3974,8 @@ static void __init setup_boot_cpu_capabilities(void)
void __init setup_boot_cpu_features(void)
{
+ init_cpu_features();
+
/*
* Initialize the indirect array of CPU capabilities pointers before we
* handle the boot CPU.
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index 1b92e52989b1..dfb21bf5541d 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -31,7 +31,6 @@
* values depending on configuration at or after reset.
*/
DEFINE_PER_CPU(struct cpuinfo_arm64, cpu_data);
-static struct cpuinfo_arm64 boot_cpu_data;
static inline const char *icache_policy_str(int l1ip)
{
@@ -528,14 +527,4 @@ void cpuinfo_store_cpu(void)
{
struct cpuinfo_arm64 *info = this_cpu_ptr(&cpu_data);
__cpuinfo_store_cpu(info);
- update_cpu_features(smp_processor_id(), info, &boot_cpu_data);
-}
-
-void __init cpuinfo_store_boot_cpu(void)
-{
- struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0);
- __cpuinfo_store_cpu(info);
-
- boot_cpu_data = *info;
- init_cpu_features(&boot_cpu_data);
}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index d2aa949e33a3..e1b15cab9ed6 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -235,6 +235,7 @@ asmlinkage notrace void secondary_start_kernel(void)
* Log the CPU info before it is marked online and might get read.
*/
cpuinfo_store_cpu();
+ update_cpu_features(cpu);
store_cpu_topology(cpu);
/*
@@ -457,7 +458,7 @@ void __init smp_prepare_boot_cpu(void)
/* Check arm64_ftr_regs[] before anything looks registers up in it. */
validate_ftr_regs();
- cpuinfo_store_boot_cpu();
+ cpuinfo_store_cpu();
setup_boot_cpu_features();
/* Conditionally switch to GIC PMR for interrupt masking */
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 11/23] arm64: smp: Defer update of secondary CPU capabilities
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (9 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 10/23] arm64: smp: Tidy up cpuinfo init and cpufeature updates Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 12/23] arm64: smp: Don't bother printing the I-cache policy for each CPU Will Deacon
` (11 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
check_local_cpu_capabilities() runs relatively early during the boot of
each secondary CPU and, despite its name, calls update_cpu_capabilities()
to manipulate the global 'system_cpucaps' based on the features detected
by the incoming CPU.
In preparation for parallel bringup of secondary CPUs, move the call
to update_cpu_capabilities() into update_cpu_features(), allowing
check_local_cpu_capabilities() to run concurrently in future, as it now
only performs local verification of the incoming CPU.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/cpufeature.c | 307 +++++++++++++++++----------------
1 file changed, 155 insertions(+), 152 deletions(-)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 5db456884ffa..1cc189944141 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -1395,154 +1395,6 @@ static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info,
return taint;
}
-/*
- * Update system wide CPU feature registers with the values from a
- * non-boot CPU. Also performs SANITY checks to make sure that there
- * aren't any insane variations from that of the boot CPU.
- */
-void update_cpu_features(int cpu)
-{
- struct cpuinfo_arm64 *boot, *info;
- int taint = 0;
-
- boot = &boot_cpu_data;
- info = per_cpu_ptr(&cpu_data, cpu);
-
- /*
- * The kernel can handle differing I-cache policies, but otherwise
- * caches should look identical. Userspace JITs will make use of
- * *minLine.
- */
- taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu,
- info->reg_ctr, boot->reg_ctr);
-
- /*
- * Userspace may perform DC ZVA instructions. Mismatched block sizes
- * could result in too much or too little memory being zeroed if a
- * process is preempted and migrated between CPUs.
- */
- taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu,
- info->reg_dczid, boot->reg_dczid);
-
- /* If different, timekeeping will be broken (especially with KVM) */
- taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu,
- info->reg_cntfrq, boot->reg_cntfrq);
-
- /*
- * The kernel uses self-hosted debug features and expects CPUs to
- * support identical debug features. We presently need CTX_CMPs, WRPs,
- * and BRPs to be identical.
- * ID_AA64DFR1 is currently RES0.
- */
- taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu,
- info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0);
- taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu,
- info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1);
- /*
- * Even in big.LITTLE, processors should be identical instruction-set
- * wise.
- */
- taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu,
- info->reg_id_aa64isar0, boot->reg_id_aa64isar0);
- taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu,
- info->reg_id_aa64isar1, boot->reg_id_aa64isar1);
- taint |= check_update_ftr_reg(SYS_ID_AA64ISAR2_EL1, cpu,
- info->reg_id_aa64isar2, boot->reg_id_aa64isar2);
- taint |= check_update_ftr_reg(SYS_ID_AA64ISAR3_EL1, cpu,
- info->reg_id_aa64isar3, boot->reg_id_aa64isar3);
-
- /*
- * Differing PARange support is fine as long as all peripherals and
- * memory are mapped within the minimum PARange of all CPUs.
- * Linux should not care about secure memory.
- */
- taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu,
- info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0);
- taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu,
- info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1);
- taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu,
- info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2);
- taint |= check_update_ftr_reg(SYS_ID_AA64MMFR3_EL1, cpu,
- info->reg_id_aa64mmfr3, boot->reg_id_aa64mmfr3);
- taint |= check_update_ftr_reg(SYS_ID_AA64MMFR4_EL1, cpu,
- info->reg_id_aa64mmfr4, boot->reg_id_aa64mmfr4);
-
- taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu,
- info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0);
- taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu,
- info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1);
- taint |= check_update_ftr_reg(SYS_ID_AA64PFR2_EL1, cpu,
- info->reg_id_aa64pfr2, boot->reg_id_aa64pfr2);
-
- taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu,
- info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0);
-
- taint |= check_update_ftr_reg(SYS_ID_AA64SMFR0_EL1, cpu,
- info->reg_id_aa64smfr0, boot->reg_id_aa64smfr0);
-
- taint |= check_update_ftr_reg(SYS_ID_AA64FPFR0_EL1, cpu,
- info->reg_id_aa64fpfr0, boot->reg_id_aa64fpfr0);
-
- /* Probe vector lengths */
- if (IS_ENABLED(CONFIG_ARM64_SVE) &&
- id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1))) {
- if (!system_capabilities_finalized()) {
- unsigned long cpacr = cpacr_save_enable_kernel_sve();
-
- vec_update_vq_map(ARM64_VEC_SVE);
-
- cpacr_restore(cpacr);
- }
- }
-
- if (IS_ENABLED(CONFIG_ARM64_SME) &&
- id_aa64pfr1_sme(read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1))) {
- unsigned long cpacr = cpacr_save_enable_kernel_sme();
-
- /* Probe vector lengths */
- if (!system_capabilities_finalized())
- vec_update_vq_map(ARM64_VEC_SME);
-
- cpacr_restore(cpacr);
- }
-
- if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
- taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu,
- info->reg_mpamidr, boot->reg_mpamidr);
-
- /*
- * The kernel uses the LDGM/STGM instructions and the number of tags
- * they read/write depends on the GMID_EL1.BS field. Check that the
- * value is the same on all CPUs.
- */
- if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
- taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu,
- info->reg_gmid, boot->reg_gmid);
-
- /*
- * If we don't have AArch32 at all then skip the checks entirely
- * as the register values may be UNKNOWN and we're not going to be
- * using them for anything.
- *
- * This relies on a sanitised view of the AArch64 ID registers
- * (e.g. SYS_ID_AA64PFR0_EL1), so we call it last.
- */
- if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
- lazy_init_32bit_cpu_features(info, boot);
- taint |= update_32bit_cpu_features(cpu, &info->aarch32,
- &boot->aarch32);
- }
-
- /*
- * Mismatched CPU features are a recipe for disaster. Don't even
- * pretend to support them.
- */
- if (taint) {
- pr_warn_once("Unsupported CPU feature variation detected.\n");
- add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
- }
-}
-
u64 read_sanitised_ftr_reg(u32 id)
{
struct arm64_ftr_reg *regp = get_arm64_ftr_reg(id);
@@ -3887,16 +3739,167 @@ void check_local_cpu_capabilities(void)
*/
check_early_cpu_features();
+ /*
+ * Verify that this CPU has all the system advertised
+ * capabilities.
+ */
+ if (system_capabilities_finalized())
+ verify_local_cpu_capabilities();
+}
+
+/*
+ * Update system wide CPU feature registers with the values from a
+ * non-boot CPU. Also performs SANITY checks to make sure that there
+ * aren't any insane variations from that of the boot CPU.
+ */
+void update_cpu_features(int cpu)
+{
+ struct cpuinfo_arm64 *boot, *info;
+ int taint = 0;
+
/*
* If we haven't finalised the system capabilities, this CPU gets
* a chance to update the errata work arounds and local features.
- * Otherwise, this CPU should verify that it has all the system
- * advertised capabilities.
*/
if (!system_capabilities_finalized())
update_cpu_capabilities(SCOPE_LOCAL_CPU);
- else
- verify_local_cpu_capabilities();
+
+ boot = &boot_cpu_data;
+ info = per_cpu_ptr(&cpu_data, cpu);
+
+ /*
+ * The kernel can handle differing I-cache policies, but otherwise
+ * caches should look identical. Userspace JITs will make use of
+ * *minLine.
+ */
+ taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu,
+ info->reg_ctr, boot->reg_ctr);
+
+ /*
+ * Userspace may perform DC ZVA instructions. Mismatched block sizes
+ * could result in too much or too little memory being zeroed if a
+ * process is preempted and migrated between CPUs.
+ */
+ taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu,
+ info->reg_dczid, boot->reg_dczid);
+
+ /* If different, timekeeping will be broken (especially with KVM) */
+ taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu,
+ info->reg_cntfrq, boot->reg_cntfrq);
+
+ /*
+ * The kernel uses self-hosted debug features and expects CPUs to
+ * support identical debug features. We presently need CTX_CMPs, WRPs,
+ * and BRPs to be identical.
+ * ID_AA64DFR1 is currently RES0.
+ */
+ taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu,
+ info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0);
+ taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu,
+ info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1);
+ /*
+ * Even in big.LITTLE, processors should be identical instruction-set
+ * wise.
+ */
+ taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu,
+ info->reg_id_aa64isar0, boot->reg_id_aa64isar0);
+ taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu,
+ info->reg_id_aa64isar1, boot->reg_id_aa64isar1);
+ taint |= check_update_ftr_reg(SYS_ID_AA64ISAR2_EL1, cpu,
+ info->reg_id_aa64isar2, boot->reg_id_aa64isar2);
+ taint |= check_update_ftr_reg(SYS_ID_AA64ISAR3_EL1, cpu,
+ info->reg_id_aa64isar3, boot->reg_id_aa64isar3);
+
+ /*
+ * Differing PARange support is fine as long as all peripherals and
+ * memory are mapped within the minimum PARange of all CPUs.
+ * Linux should not care about secure memory.
+ */
+ taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu,
+ info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0);
+ taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu,
+ info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1);
+ taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu,
+ info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2);
+ taint |= check_update_ftr_reg(SYS_ID_AA64MMFR3_EL1, cpu,
+ info->reg_id_aa64mmfr3, boot->reg_id_aa64mmfr3);
+ taint |= check_update_ftr_reg(SYS_ID_AA64MMFR4_EL1, cpu,
+ info->reg_id_aa64mmfr4, boot->reg_id_aa64mmfr4);
+
+ taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu,
+ info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0);
+ taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu,
+ info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1);
+ taint |= check_update_ftr_reg(SYS_ID_AA64PFR2_EL1, cpu,
+ info->reg_id_aa64pfr2, boot->reg_id_aa64pfr2);
+
+ taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu,
+ info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0);
+
+ taint |= check_update_ftr_reg(SYS_ID_AA64SMFR0_EL1, cpu,
+ info->reg_id_aa64smfr0, boot->reg_id_aa64smfr0);
+
+ taint |= check_update_ftr_reg(SYS_ID_AA64FPFR0_EL1, cpu,
+ info->reg_id_aa64fpfr0, boot->reg_id_aa64fpfr0);
+
+ /* Probe vector lengths */
+ if (IS_ENABLED(CONFIG_ARM64_SVE) &&
+ id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1))) {
+ if (!system_capabilities_finalized()) {
+ unsigned long cpacr = cpacr_save_enable_kernel_sve();
+
+ vec_update_vq_map(ARM64_VEC_SVE);
+
+ cpacr_restore(cpacr);
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_ARM64_SME) &&
+ id_aa64pfr1_sme(read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1))) {
+ unsigned long cpacr = cpacr_save_enable_kernel_sme();
+
+ /* Probe vector lengths */
+ if (!system_capabilities_finalized())
+ vec_update_vq_map(ARM64_VEC_SME);
+
+ cpacr_restore(cpacr);
+ }
+
+ if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
+ taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu,
+ info->reg_mpamidr, boot->reg_mpamidr);
+
+ /*
+ * The kernel uses the LDGM/STGM instructions and the number of tags
+ * they read/write depends on the GMID_EL1.BS field. Check that the
+ * value is the same on all CPUs.
+ */
+ if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
+ taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu,
+ info->reg_gmid, boot->reg_gmid);
+
+ /*
+ * If we don't have AArch32 at all then skip the checks entirely
+ * as the register values may be UNKNOWN and we're not going to be
+ * using them for anything.
+ *
+ * This relies on a sanitised view of the AArch64 ID registers
+ * (e.g. SYS_ID_AA64PFR0_EL1), so we call it last.
+ */
+ if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
+ lazy_init_32bit_cpu_features(info, boot);
+ taint |= update_32bit_cpu_features(cpu, &info->aarch32,
+ &boot->aarch32);
+ }
+
+ /*
+ * Mismatched CPU features are a recipe for disaster. Don't even
+ * pretend to support them.
+ */
+ if (taint) {
+ pr_warn_once("Unsupported CPU feature variation detected.\n");
+ add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
+ }
}
bool this_cpu_has_cap(unsigned int n)
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 12/23] arm64: smp: Don't bother printing the I-cache policy for each CPU
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (10 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 11/23] arm64: smp: Defer update of secondary CPU capabilities Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 13/23] arm64: smp: Defer RCU registration during secondary CPU bringup Will Deacon
` (10 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
The I-cache policy isn't particularly interesting but printing it early
can result in unnecessary serialisation of onlining CPUs.
Remove the pointless print.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/cpuinfo.c | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index dfb21bf5541d..94e6cd211d70 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -31,19 +31,6 @@
* values depending on configuration at or after reset.
*/
DEFINE_PER_CPU(struct cpuinfo_arm64, cpu_data);
-
-static inline const char *icache_policy_str(int l1ip)
-{
- switch (l1ip) {
- case CTR_EL0_L1Ip_VIPT:
- return "VIPT";
- case CTR_EL0_L1Ip_PIPT:
- return "PIPT";
- default:
- return "RESERVED/UNKNOWN";
- }
-}
-
unsigned long __icache_flags;
static const char *const hwcap_str[] = {
@@ -424,7 +411,6 @@ device_initcall(cpuinfo_regs_init);
static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
{
- unsigned int cpu = smp_processor_id();
u32 l1ip = CTR_L1IP(info->reg_ctr);
switch (l1ip) {
@@ -436,8 +422,6 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
set_bit(ICACHEF_ALIASING, &__icache_flags);
break;
}
-
- pr_info("Detected %s I-cache on CPU%d\n", icache_policy_str(l1ip), cpu);
}
static void __cpuinfo_store_cpu_32bit(struct cpuinfo_32bit *info)
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 13/23] arm64: smp: Defer RCU registration during secondary CPU bringup
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (11 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 12/23] arm64: smp: Don't bother printing the I-cache policy for each CPU Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 14/23] arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU onlining Will Deacon
` (9 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Calling rcutree_report_cpu_starting() early during boot can lead to
livelocks with the generic CPU hotplug mechanism if the boot CPU blocks
on an RCU grace period while the CPU being onlined is spinning in
cpuhp_ap_sync_alive().
In preparation for enabling the generic CPU hotplug code on arm64, split
up the trace_hardirqs_off() call during secondary CPU bringup so that we
update lockdep early but defer the tracing updates until after the new
CPU has been registered with RCU, which itself is deferred until after
the early CPU compatability checks, allowing us to drop the explicit
call to rcutree_report_cpu_dead() entirely.
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/smp.c | 6 +++---
include/linux/rcutree.h | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index e1b15cab9ed6..014b163ce658 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -217,8 +217,7 @@ asmlinkage notrace void secondary_start_kernel(void)
if (system_uses_irq_prio_masking())
init_gic_priority_masking();
- rcutree_report_cpu_starting(cpu);
- trace_hardirqs_off();
+ lockdep_hardirqs_off(CALLER_ADDR0);
/*
* If the system has established the capabilities, make sure
@@ -235,6 +234,7 @@ asmlinkage notrace void secondary_start_kernel(void)
* Log the CPU info before it is marked online and might get read.
*/
cpuinfo_store_cpu();
+ rcutree_report_cpu_starting(cpu);
update_cpu_features(cpu);
store_cpu_topology(cpu);
@@ -242,6 +242,7 @@ asmlinkage notrace void secondary_start_kernel(void)
* Enable GIC and timers.
*/
notify_cpu_starting(cpu);
+ trace_hardirqs_off_finish();
ipi_setup(cpu);
@@ -411,7 +412,6 @@ void __noreturn cpu_die_early(void)
/* Mark this CPU absent */
set_cpu_present(cpu, 0);
- rcutree_report_cpu_dead();
if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
update_cpu_boot_status(CPU_KILL_ME);
diff --git a/include/linux/rcutree.h b/include/linux/rcutree.h
index 16a04202888b..e6ad4f2a475c 100644
--- a/include/linux/rcutree.h
+++ b/include/linux/rcutree.h
@@ -116,7 +116,7 @@ int rcutree_offline_cpu(unsigned int cpu);
void rcutree_migrate_callbacks(int cpu);
-/* Called from hotplug and also arm64 early secondary boot failure */
+/* Called from hotplug */
void rcutree_report_cpu_dead(void);
#endif /* __LINUX_RCUTREE_H */
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 14/23] arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU onlining
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (12 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 13/23] arm64: smp: Defer RCU registration during secondary CPU bringup Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 15/23] arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP " Will Deacon
` (8 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Select HOTPLUG_CORE_SYNC_FULL on arm64 to replace the 'cpu_running'
completion with the generic code for synchronising with secondary CPUs
during boot.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/Kconfig | 2 +-
arch/arm64/include/asm/smp.h | 1 -
arch/arm64/kernel/smp.c | 39 +++++++++++++++++-------------------
3 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef944..89d1f0f2269c 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -230,7 +230,7 @@ config ARM64
select HAVE_SYSCALL_TRACEPOINTS
select HAVE_KPROBES
select HAVE_KRETPROBES
- select HOTPLUG_CORE_SYNC_DEAD if HOTPLUG_CPU
+ select HOTPLUG_CORE_SYNC_FULL
select HOTPLUG_SMT if HOTPLUG_CPU
select IRQ_DOMAIN
select IRQ_FORCED_THREADING
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 10ea4f543069..fe343c30d620 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -12,7 +12,6 @@
#define CPU_BOOT_STATUS_MASK ((UL(1) << CPU_STUCK_REASON_SHIFT) - 1)
#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 */
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 014b163ce658..d83ba5bbfd37 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -108,12 +108,9 @@ static int boot_secondary(unsigned int cpu, struct task_struct *idle)
return -EOPNOTSUPP;
}
-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
@@ -124,27 +121,24 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
/* Now bring the CPU into our world */
ret = boot_secondary(cpu, idle);
- if (ret) {
- if (ret != -EPERM)
- pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
- return ret;
- }
+ if (ret && ret != -EPERM)
+ pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
+ return ret;
+}
- /*
- * CPU was successfully started, wait for it to come online or
- * time out.
- */
- wait_for_completion_timeout(&cpu_running,
- msecs_to_jiffies(5000));
- if (cpu_online(cpu))
- return 0;
+void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
+{
+ long status;
+
+ if (is_alive)
+ return;
- pr_crit("CPU%u: failed to come online\n", cpu);
secondary_data.task = NULL;
status = READ_ONCE(secondary_data.status);
if (status == CPU_MMU_OFF)
status = READ_ONCE(__early_cpu_boot_status);
+ /* A CPU has failed to boot. Try to figure out what happened. */
switch (status & CPU_BOOT_STATUS_MASK) {
default:
pr_err("CPU%u: failed in unknown state : 0x%lx\n",
@@ -171,8 +165,6 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
case CPU_PANIC_KERNEL:
panic("CPU%u detected unsupported configuration\n", cpu);
}
-
- return -EIO;
}
static void init_gic_priority_masking(void)
@@ -234,6 +226,13 @@ asmlinkage notrace void secondary_start_kernel(void)
* Log the CPU info before it is marked online and might get read.
*/
cpuinfo_store_cpu();
+
+ /*
+ * Synchronise with the core bringing us online so that it knows
+ * we made it into the kernel. We're still not 'online'.
+ */
+ cpuhp_ap_sync_alive();
+
rcutree_report_cpu_starting(cpu);
update_cpu_features(cpu);
store_cpu_topology(cpu);
@@ -256,9 +255,7 @@ asmlinkage notrace void secondary_start_kernel(void)
pr_info("CPU%u: Booted secondary processor 0x%010lx [0x%08x]\n",
cpu, (unsigned long)mpidr,
read_cpuid_id());
- update_cpu_boot_status(CPU_BOOT_SUCCESS);
set_cpu_online(cpu, true);
- complete(&cpu_running);
/*
* Secondary CPUs enter the kernel with all DAIF exceptions masked.
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 15/23] arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP machinery for CPU onlining
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (13 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 14/23] arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU onlining Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 16/23] arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu Will Deacon
` (7 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
In preparation for enabling parallel bringup of secondary CPUs on arm64,
take the baby step of moving from HOTPLUG_CORE_SYNC_FULL to
HOTPLUG_SPLIT_STARTUP.
Rework the cpu_die_early() path to use a private cpumask, otherwise
clearing the incoming CPU from the present mask in the 'kick' stage will
prevent the hotplug stage machine from progressing and
arch_cpuhp_cleanup_kick_cpu() will not be called.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/Kconfig | 2 +-
arch/arm64/include/asm/smp.h | 1 +
arch/arm64/kernel/smp.c | 10 ++++++----
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 89d1f0f2269c..fd8cf792b7fd 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -230,7 +230,7 @@ config ARM64
select HAVE_SYSCALL_TRACEPOINTS
select HAVE_KPROBES
select HAVE_KRETPROBES
- select HOTPLUG_CORE_SYNC_FULL
+ select HOTPLUG_SPLIT_STARTUP
select HOTPLUG_SMT if HOTPLUG_CPU
select IRQ_DOMAIN
select IRQ_FORCED_THREADING
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index fe343c30d620..7b986a6a765b 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -89,6 +89,7 @@ asmlinkage void secondary_start_kernel(void);
struct secondary_data {
struct task_struct *task;
long status;
+ cpumask_t cpu_died_early_mask;
};
extern struct secondary_data secondary_data;
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index d83ba5bbfd37..918b54d1c301 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -62,7 +62,7 @@
* so we need some other way of telling a new secondary core
* where to place its SVC stack
*/
-struct secondary_data secondary_data;
+struct secondary_data secondary_data = {};
/* Number of CPUs which aren't online, but looping in kernel text. */
static int cpus_stuck_in_kernel;
@@ -108,7 +108,7 @@ static int boot_secondary(unsigned int cpu, struct task_struct *idle)
return -EOPNOTSUPP;
}
-int __cpu_up(unsigned int cpu, struct task_struct *idle)
+int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
{
int ret;
@@ -138,6 +138,9 @@ void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
if (status == CPU_MMU_OFF)
status = READ_ONCE(__early_cpu_boot_status);
+ if (cpumask_test_and_clear_cpu(cpu, &secondary_data.cpu_died_early_mask))
+ set_cpu_present(cpu, false);
+
/* A CPU has failed to boot. Try to figure out what happened. */
switch (status & CPU_BOOT_STATUS_MASK) {
default:
@@ -407,8 +410,7 @@ void __noreturn cpu_die_early(void)
pr_crit("CPU%d: will not boot\n", cpu);
- /* Mark this CPU absent */
- set_cpu_present(cpu, 0);
+ cpumask_set_cpu(cpu, &secondary_data.cpu_died_early_mask);
if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
update_cpu_boot_status(CPU_KILL_ME);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 16/23] arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (14 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 15/23] arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP " Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 17/23] arm64: cpu_ops: Introduce get_secondary_cpu_ops() Will Deacon
` (6 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
'cpu_ops' is an NR_CPUS-length array of 'cpu_operations' pointers, which
theoretically allows for different CPUs to have different bringup and
hotplug backends.
In reality, this complexity exists only to deal with the case where CPU0
is not hotpluggable, so replace the array with a single, global pointer
and record separately whether or not they apply to the boot CPU. Update
the logic in init_cpu_ops() to enforce that only a single set of
'cpu_ops' is required.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/cpu_ops.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c
index e133011f64b5..eacfb88a0c0c 100644
--- a/arch/arm64/kernel/cpu_ops.c
+++ b/arch/arm64/kernel/cpu_ops.c
@@ -20,7 +20,8 @@ extern const struct cpu_operations acpi_parking_protocol_ops;
#endif
extern const struct cpu_operations cpu_psci_ops;
-static const struct cpu_operations *cpu_ops[NR_CPUS] __ro_after_init;
+static const struct cpu_operations *cpu_ops __ro_after_init;
+static bool boot_cpu_has_enable_method __ro_after_init;
static const struct cpu_operations *const dt_supported_cpu_ops[] __initconst = {
&smp_spin_table_ops,
@@ -40,6 +41,9 @@ static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
const struct cpu_operations *const *ops;
+ if (!name)
+ return NULL;
+
ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops;
while (*ops) {
@@ -49,6 +53,7 @@ static const struct cpu_operations * __init cpu_get_ops(const char *name)
ops++;
}
+ pr_warn("Unsupported enable-method: %s\n", name);
return NULL;
}
@@ -94,25 +99,31 @@ static const char *__init cpu_read_enable_method(int cpu)
return enable_method;
}
/*
- * Read a cpu's enable method and record it in cpu_ops.
+ * Read a cpu's enable method and update/check cpu_ops.
*/
int __init init_cpu_ops(int cpu)
{
const char *enable_method = cpu_read_enable_method(cpu);
+ const struct cpu_operations *ops = cpu_get_ops(enable_method);
- if (!enable_method)
+ if (!ops)
return -ENODEV;
- cpu_ops[cpu] = cpu_get_ops(enable_method);
- if (!cpu_ops[cpu]) {
- pr_warn("Unsupported enable-method: %s\n", enable_method);
- return -EOPNOTSUPP;
- }
+ if (!cpu_ops)
+ cpu_ops = ops;
+ else if (cpu_ops != ops)
+ return -EBUSY;
+
+ if (cpu == 0)
+ boot_cpu_has_enable_method = true;
return 0;
}
const struct cpu_operations *get_cpu_ops(int cpu)
{
- return cpu_ops[cpu];
+ if (cpu || boot_cpu_has_enable_method)
+ return cpu_ops;
+
+ return NULL;
}
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 17/23] arm64: cpu_ops: Introduce get_secondary_cpu_ops()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (15 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 16/23] arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 18/23] firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs Will Deacon
` (5 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Introduce get_secondary_cpu_ops() to retrieve a pointer to the
'cpu_operations' structure for the non-boot CPUs and use it instead of
get_cpu_ops() where we are dealing with secondary CPUs.
This is a pre-requisite for enabling parallel CPU bring-up.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpu_ops.h | 1 +
arch/arm64/kernel/cpu_ops.c | 5 +++++
arch/arm64/kernel/smp.c | 19 +++++++------------
3 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/include/asm/cpu_ops.h b/arch/arm64/include/asm/cpu_ops.h
index a444c8915e88..cd298a8710d8 100644
--- a/arch/arm64/include/asm/cpu_ops.h
+++ b/arch/arm64/include/asm/cpu_ops.h
@@ -48,6 +48,7 @@ struct cpu_operations {
int __init init_cpu_ops(int cpu);
extern const struct cpu_operations *get_cpu_ops(int cpu);
+extern const struct cpu_operations *get_secondary_cpu_ops(void);
static inline void __init init_bootcpu_ops(void)
{
diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c
index eacfb88a0c0c..7d183ca31dc8 100644
--- a/arch/arm64/kernel/cpu_ops.c
+++ b/arch/arm64/kernel/cpu_ops.c
@@ -127,3 +127,8 @@ const struct cpu_operations *get_cpu_ops(int cpu)
return NULL;
}
+
+const struct cpu_operations *get_secondary_cpu_ops(void)
+{
+ return cpu_ops;
+}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 918b54d1c301..f7141ec18029 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -100,7 +100,7 @@ static inline int op_cpu_kill(unsigned int cpu)
*/
static int boot_secondary(unsigned int cpu, struct task_struct *idle)
{
- const struct cpu_operations *ops = get_cpu_ops(cpu);
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
if (ops->cpu_boot)
return ops->cpu_boot(cpu);
@@ -221,7 +221,7 @@ asmlinkage notrace void secondary_start_kernel(void)
*/
check_local_cpu_capabilities();
- ops = get_cpu_ops(cpu);
+ ops = get_secondary_cpu_ops();
if (ops->cpu_postboot)
ops->cpu_postboot();
@@ -329,7 +329,7 @@ int __cpu_disable(void)
static int op_cpu_kill(unsigned int cpu)
{
- const struct cpu_operations *ops = get_cpu_ops(cpu);
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
/*
* If we have no means of synchronising with the dying CPU, then assume
@@ -370,7 +370,7 @@ void arch_cpuhp_cleanup_dead_cpu(unsigned int cpu)
void __noreturn cpu_die(void)
{
unsigned int cpu = smp_processor_id();
- const struct cpu_operations *ops = get_cpu_ops(cpu);
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
idle_task_exit();
@@ -496,7 +496,7 @@ static int __init smp_cpu_setup(int cpu)
if (init_cpu_ops(cpu))
return -ENODEV;
- ops = get_cpu_ops(cpu);
+ ops = get_secondary_cpu_ops();
if (ops->cpu_init(cpu))
return -ENODEV;
@@ -780,7 +780,7 @@ void __init smp_init_cpus(void)
void __init smp_prepare_cpus(unsigned int max_cpus)
{
- const struct cpu_operations *ops;
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
unsigned int cpu;
int err;
@@ -806,10 +806,6 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
if (cpu == 0)
continue;
- ops = get_cpu_ops(cpu);
- if (!ops)
- continue;
-
err = ops->cpu_prepare(cpu);
if (err)
continue;
@@ -1345,8 +1341,7 @@ bool smp_crash_stop_failed(void)
static bool have_cpu_die(void)
{
#ifdef CONFIG_HOTPLUG_CPU
- int any_cpu = raw_smp_processor_id();
- const struct cpu_operations *ops = get_cpu_ops(any_cpu);
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
if (ops && ops->cpu_die)
return true;
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 18/23] firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (16 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 17/23] arm64: cpu_ops: Introduce get_secondary_cpu_ops() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 19/23] firmware/psci: Extend ->cpu_on() callback to take an additional argument Will Deacon
` (4 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Secure monitor calls to EL3 aren't necessarily cheap, so cache the
PSCI version number in memory to avoid asking firmware the same question
over and over again.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
drivers/firmware/psci/psci.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
index e73bae6cb23a..8bb3f7c37678 100644
--- a/drivers/firmware/psci/psci.c
+++ b/drivers/firmware/psci/psci.c
@@ -155,7 +155,12 @@ static u32 psci_0_1_get_version(void)
static u32 psci_0_2_get_version(void)
{
- return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
+ static u32 version;
+
+ if (unlikely(!version))
+ version = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
+
+ return version;
}
int psci_set_osi_mode(bool enable)
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 19/23] firmware/psci: Extend ->cpu_on() callback to take an additional argument
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (17 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 18/23] firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 20/23] arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot() Will Deacon
` (3 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
In preparation for hooking up the 'context ID' parameter introduced by
PSCI v0.2 to the CPU_ON call, extend the ->cpu_on() callback to take
an additional argument and modify all callers to pass 0 for now.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm/kernel/psci_smp.c | 4 ++--
arch/arm64/kernel/psci.c | 2 +-
drivers/firmware/psci/psci.c | 20 ++++++++++++++------
include/linux/psci.h | 3 ++-
4 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/arch/arm/kernel/psci_smp.c b/arch/arm/kernel/psci_smp.c
index 3bb0c4dcfc5c..bf05dbf433b1 100644
--- a/arch/arm/kernel/psci_smp.c
+++ b/arch/arm/kernel/psci_smp.c
@@ -49,10 +49,10 @@ static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle)
return psci_ops.cpu_on(cpu_logical_map(cpu),
((phys_addr_t)(&secondary_startup)
- XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
- + CONFIG_XIP_PHYS_ADDR));
+ + CONFIG_XIP_PHYS_ADDR), 0);
#else
return psci_ops.cpu_on(cpu_logical_map(cpu),
- virt_to_idmap(&secondary_startup));
+ virt_to_idmap(&secondary_startup), 0);
#endif
return -ENODEV;
}
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c
index fabd732d0a2d..6b25a12ed143 100644
--- a/arch/arm64/kernel/psci.c
+++ b/arch/arm64/kernel/psci.c
@@ -39,7 +39,7 @@ static int __init cpu_psci_cpu_prepare(unsigned int cpu)
static int cpu_psci_cpu_boot(unsigned int cpu)
{
phys_addr_t pa_secondary_entry = __pa_symbol(secondary_entry);
- int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry);
+ int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry, 0);
if (err && err != -EPERM)
pr_err("failed to boot CPU%d (%d)\n", cpu, err);
diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
index 8bb3f7c37678..95034485cb4d 100644
--- a/drivers/firmware/psci/psci.c
+++ b/drivers/firmware/psci/psci.c
@@ -219,22 +219,30 @@ static int psci_0_2_cpu_off(u32 state)
return __psci_cpu_off(PSCI_0_2_FN_CPU_OFF, state);
}
-static int __psci_cpu_on(u32 fn, unsigned long cpuid, unsigned long entry_point)
+static int __psci_cpu_on(u32 fn, unsigned long cpuid, unsigned long entry_point,
+ unsigned long context)
{
int err;
- err = invoke_psci_fn(fn, cpuid, entry_point, 0);
+ err = invoke_psci_fn(fn, cpuid, entry_point, context);
return psci_to_linux_errno(err);
}
-static int psci_0_1_cpu_on(unsigned long cpuid, unsigned long entry_point)
+static int psci_0_1_cpu_on(unsigned long cpuid, unsigned long entry_point,
+ unsigned long mbz)
{
- return __psci_cpu_on(psci_0_1_function_ids.cpu_on, cpuid, entry_point);
+ if (mbz)
+ return -EINVAL;
+
+ return __psci_cpu_on(psci_0_1_function_ids.cpu_on, cpuid, entry_point,
+ 0);
}
-static int psci_0_2_cpu_on(unsigned long cpuid, unsigned long entry_point)
+static int psci_0_2_cpu_on(unsigned long cpuid, unsigned long entry_point,
+ unsigned long context)
{
- return __psci_cpu_on(PSCI_FN_NATIVE(0_2, CPU_ON), cpuid, entry_point);
+ return __psci_cpu_on(PSCI_FN_NATIVE(0_2, CPU_ON), cpuid, entry_point,
+ context);
}
static int __psci_migrate(u32 fn, unsigned long cpuid)
diff --git a/include/linux/psci.h b/include/linux/psci.h
index 4ca0060a3fc4..0f43868a0bee 100644
--- a/include/linux/psci.h
+++ b/include/linux/psci.h
@@ -25,7 +25,8 @@ struct psci_operations {
u32 (*get_version)(void);
int (*cpu_suspend)(u32 state, unsigned long entry_point);
int (*cpu_off)(u32 state);
- int (*cpu_on)(unsigned long cpuid, unsigned long entry_point);
+ int (*cpu_on)(unsigned long cpuid, unsigned long entry_point,
+ unsigned long context);
int (*migrate)(unsigned long cpuid);
int (*affinity_info)(unsigned long target_affinity,
unsigned long lowest_affinity_level);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 20/23] arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot()
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (18 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 19/23] firmware/psci: Extend ->cpu_on() callback to take an additional argument Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 21/23] arm64: smp: Pass secondary CPU boot parameters via firmware if possible Will Deacon
` (2 subsequent siblings)
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Some backend implementations of 'struct cpu_ops', notably PSCI v0.2+,
allow an optional argument to be passed in register X0 to the target
CPU during boot.
Expose this functionality by extending the ->cpu_boot() CPU operation
to take an additional argument which is ignored unless the new optional
->cpu_boot_has_arg() callback is present and returns 'true'. For now,
we continue to pass zero.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/cpu_ops.h | 6 +++++-
arch/arm64/kernel/acpi_parking_protocol.c | 3 ++-
arch/arm64/kernel/psci.c | 12 ++++++++++--
arch/arm64/kernel/smp.c | 2 +-
arch/arm64/kernel/smp_spin_table.c | 2 +-
5 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/include/asm/cpu_ops.h b/arch/arm64/include/asm/cpu_ops.h
index cd298a8710d8..e7662a1879d9 100644
--- a/arch/arm64/include/asm/cpu_ops.h
+++ b/arch/arm64/include/asm/cpu_ops.h
@@ -21,6 +21,9 @@
* mechanism for doing so, tests whether it is possible to boot
* the given CPU.
* @cpu_boot: Boots a cpu into the kernel.
+ * @cpu_boot_has_arg: Optionally determines whether @cpu_boot passes its
+ * (non-zero) second argument to the booting CPU in
+ * register x0.
* @cpu_postboot: Optionally, perform any post-boot cleanup or necessary
* synchronisation. Called from the cpu being booted.
* @cpu_can_disable: Determines whether a CPU can be disabled based on
@@ -36,7 +39,8 @@ struct cpu_operations {
const char *name;
int (*cpu_init)(unsigned int);
int (*cpu_prepare)(unsigned int);
- int (*cpu_boot)(unsigned int);
+ int (*cpu_boot)(unsigned int, unsigned long);
+ bool (*cpu_boot_has_arg)(void);
void (*cpu_postboot)(void);
#ifdef CONFIG_HOTPLUG_CPU
bool (*cpu_can_disable)(unsigned int cpu);
diff --git a/arch/arm64/kernel/acpi_parking_protocol.c b/arch/arm64/kernel/acpi_parking_protocol.c
index e1be29e608b7..24ebde1241bf 100644
--- a/arch/arm64/kernel/acpi_parking_protocol.c
+++ b/arch/arm64/kernel/acpi_parking_protocol.c
@@ -56,7 +56,8 @@ static int acpi_parking_protocol_cpu_prepare(unsigned int cpu)
return 0;
}
-static int acpi_parking_protocol_cpu_boot(unsigned int cpu)
+static int acpi_parking_protocol_cpu_boot(unsigned int cpu,
+ unsigned long ignored)
{
struct cpu_mailbox_entry *cpu_entry = &cpu_mailbox_entries[cpu];
struct parking_protocol_mailbox __iomem *mailbox;
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c
index 6b25a12ed143..8cd74a77de2e 100644
--- a/arch/arm64/kernel/psci.c
+++ b/arch/arm64/kernel/psci.c
@@ -36,16 +36,23 @@ static int __init cpu_psci_cpu_prepare(unsigned int cpu)
return 0;
}
-static int cpu_psci_cpu_boot(unsigned int cpu)
+static int cpu_psci_cpu_boot(unsigned int cpu, unsigned long context)
{
phys_addr_t pa_secondary_entry = __pa_symbol(secondary_entry);
- int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry, 0);
+ int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry,
+ context);
if (err && err != -EPERM)
pr_err("failed to boot CPU%d (%d)\n", cpu, err);
return err;
}
+static bool cpu_psci_cpu_boot_has_context(void)
+{
+ return psci_ops.get_version &&
+ psci_ops.get_version() >= PSCI_VERSION(0, 2);
+}
+
#ifdef CONFIG_HOTPLUG_CPU
static bool cpu_psci_cpu_can_disable(unsigned int cpu)
{
@@ -114,6 +121,7 @@ const struct cpu_operations cpu_psci_ops = {
.cpu_init = cpu_psci_cpu_init,
.cpu_prepare = cpu_psci_cpu_prepare,
.cpu_boot = cpu_psci_cpu_boot,
+ .cpu_boot_has_arg = cpu_psci_cpu_boot_has_context,
#ifdef CONFIG_HOTPLUG_CPU
.cpu_can_disable = cpu_psci_cpu_can_disable,
.cpu_disable = cpu_psci_cpu_disable,
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index f7141ec18029..29c70616a107 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -103,7 +103,7 @@ static int boot_secondary(unsigned int cpu, struct task_struct *idle)
const struct cpu_operations *ops = get_secondary_cpu_ops();
if (ops->cpu_boot)
- return ops->cpu_boot(cpu);
+ return ops->cpu_boot(cpu, 0);
return -EOPNOTSUPP;
}
diff --git a/arch/arm64/kernel/smp_spin_table.c b/arch/arm64/kernel/smp_spin_table.c
index 49029eace3ad..a5e6f444c25f 100644
--- a/arch/arm64/kernel/smp_spin_table.c
+++ b/arch/arm64/kernel/smp_spin_table.c
@@ -104,7 +104,7 @@ static int smp_spin_table_cpu_prepare(unsigned int cpu)
return 0;
}
-static int smp_spin_table_cpu_boot(unsigned int cpu)
+static int smp_spin_table_cpu_boot(unsigned int cpu, unsigned long ignored)
{
/*
* Update the pen release flag.
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 21/23] arm64: smp: Pass secondary CPU boot parameters via firmware if possible
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (19 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 20/23] arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot() Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 22/23] arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining Will Deacon
2026-10-09 10:07 ` [PATCH v2 23/23] arm64: smp: Harden parallel CPU bringup against broken PSCI firmware Will Deacon
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
In preparation for parallel bringup of secondary CPUs, the global
'secondary_data' structure used for initial paramater passing must be
localised.
Pass the idle 'task_struct' pointer for secondary CPUs directly to
->cpu_boot() if the backend supports it.
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/smp.h | 1 +
arch/arm64/kernel/head.S | 12 +++++++++++-
arch/arm64/kernel/psci.c | 4 ++--
arch/arm64/kernel/smp.c | 25 +++++++++++--------------
4 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 7b986a6a765b..7f2cd84b7785 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -95,6 +95,7 @@ struct secondary_data {
extern struct secondary_data secondary_data;
extern long __early_cpu_boot_status;
extern void secondary_entry(void);
+extern void secondary_entry_with_arg(void);
extern void arch_send_call_function_single_ipi(int cpu);
extern void arch_send_call_function_ipi_mask(const struct cpumask *mask);
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 87a822e5c4ca..17868b497d7c 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -335,6 +335,7 @@ SYM_FUNC_END(init_kernel_el)
* cores are held until we're ready for them to initialise.
*/
SYM_FUNC_START(secondary_holding_pen)
+ mov x19, xzr
mov x0, xzr
bl init_kernel_el // w0=cpu_boot_mode
mrs x2, mpidr_el1
@@ -353,10 +354,16 @@ SYM_FUNC_END(secondary_holding_pen)
* be used where CPUs are brought online dynamically by the kernel.
*/
SYM_FUNC_START(secondary_entry)
+ mov x0, xzr
+ b secondary_entry_with_arg
+SYM_FUNC_END(secondary_entry)
+
+SYM_FUNC_START(secondary_entry_with_arg)
+ mov x19, x0
mov x0, xzr
bl init_kernel_el // w0=cpu_boot_mode
b secondary_startup
-SYM_FUNC_END(secondary_entry)
+SYM_FUNC_END(secondary_entry_with_arg)
SYM_FUNC_START_LOCAL(secondary_startup)
/*
@@ -391,10 +398,13 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
msr vbar_el1, x5
isb
+ mov x2, x19
+ cbnz x2, 1f
adr_l x0, secondary_data
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slow
+1:
init_cpu_task x2, x1, x3
#ifdef CONFIG_ARM64_PTR_AUTH
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c
index 8cd74a77de2e..54aa91039b69 100644
--- a/arch/arm64/kernel/psci.c
+++ b/arch/arm64/kernel/psci.c
@@ -38,8 +38,8 @@ static int __init cpu_psci_cpu_prepare(unsigned int cpu)
static int cpu_psci_cpu_boot(unsigned int cpu, unsigned long context)
{
- phys_addr_t pa_secondary_entry = __pa_symbol(secondary_entry);
- int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry,
+ void *entry_va = context ? secondary_entry_with_arg : secondary_entry;
+ int err = psci_ops.cpu_on(cpu_logical_map(cpu), __pa_symbol(entry_va),
context);
if (err && err != -EPERM)
pr_err("failed to boot CPU%d (%d)\n", cpu, err);
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 29c70616a107..fb3a93090a3c 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -93,34 +93,31 @@ static inline int op_cpu_kill(unsigned int cpu)
}
#endif
-
/*
* Boot a secondary CPU, and assign it the specified idle task.
* This also gives us the initial stack to use for this CPU.
*/
-static int boot_secondary(unsigned int cpu, struct task_struct *idle)
-{
- const struct cpu_operations *ops = get_secondary_cpu_ops();
-
- if (ops->cpu_boot)
- return ops->cpu_boot(cpu, 0);
-
- return -EOPNOTSUPP;
-}
-
int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
{
- int ret;
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
+ int ret = -EOPNOTSUPP;
+ void *arg = NULL;
/*
* We need to tell the secondary core where to find its stack and the
* page tables.
*/
- secondary_data.task = idle;
+ if (ops->cpu_boot_has_arg && ops->cpu_boot_has_arg())
+ arg = idle;
+ else
+ secondary_data.task = idle;
+
update_cpu_boot_status(CPU_MMU_OFF);
/* Now bring the CPU into our world */
- ret = boot_secondary(cpu, idle);
+ if (ops->cpu_boot)
+ ret = ops->cpu_boot(cpu, (unsigned long)arg);
+
if (ret && ret != -EPERM)
pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
return ret;
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 22/23] arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (20 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 21/23] arm64: smp: Pass secondary CPU boot parameters via firmware if possible Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 23/23] arm64: smp: Harden parallel CPU bringup against broken PSCI firmware Will Deacon
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Make the move from HOTPLUG_SPLIT_STARTUP to HOTPLUG_PARALLEL and
enable parallel CPU bringup on systems with PSCI v0.2 or later.
The fiddly part of all this is the error handling if a CPU fails to
come up, as we can no longer rely on a single global 'status' flag to
capture the details. Instead, the secondary_data::status field is
replaced with a zero-initialised byte array, with each byte representing
an error reason, so the total set of failures can be accurately captured
by the primary CPU.
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/Kconfig | 2 +-
arch/arm64/include/asm/smp.h | 35 +++++++------
arch/arm64/include/asm/topology.h | 2 +
arch/arm64/kernel/head.S | 13 ++---
arch/arm64/kernel/smp.c | 82 ++++++++++++++++---------------
arch/arm64/mm/mmu.c | 2 +-
6 files changed, 71 insertions(+), 65 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fd8cf792b7fd..8d963cec7189 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -230,7 +230,7 @@ config ARM64
select HAVE_SYSCALL_TRACEPOINTS
select HAVE_KPROBES
select HAVE_KRETPROBES
- select HOTPLUG_SPLIT_STARTUP
+ select HOTPLUG_PARALLEL
select HOTPLUG_SMT if HOTPLUG_CPU
select IRQ_DOMAIN
select IRQ_FORCED_THREADING
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 7f2cd84b7785..3decb42164aa 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -7,20 +7,15 @@
#include <linux/const.h>
-/* Values for secondary_data.status */
-#define CPU_STUCK_REASON_SHIFT (8)
-#define CPU_BOOT_STATUS_MASK ((UL(1) << CPU_STUCK_REASON_SHIFT) - 1)
+/* Offsets for early CPU boot reasons */
+#define EARLY_CPU_STUCK_REASON_52_BIT_VA (0)
+#define EARLY_CPU_STUCK_REASON_NO_GRAN (1)
+#define EARLY_CPU_STUCK_REASON_MAX (2)
-#define CPU_MMU_OFF (-1)
-/* 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)
+/* Offsets for late (i.e. MMU-enabled) CPU boot reasons */
/* Fatal system error detected by secondary CPU, crash the system */
-#define CPU_PANIC_KERNEL (3)
-
-#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)
+#define CPU_PANIC_KERNEL (0)
+#define CPU_STATUS_FLAGS_MAX (1)
#ifndef __ASSEMBLER__
@@ -81,6 +76,14 @@ static inline void set_smp_ipi_range(int ipi_base, int n)
*/
asmlinkage void secondary_start_kernel(void);
+union secondary_status {
+ u64 val;
+ union {
+ u8 flags[CPU_STATUS_FLAGS_MAX];
+ u8 early_flags[EARLY_CPU_STUCK_REASON_MAX];
+ };
+};
+
/*
* Initial data for bringing up a secondary CPU.
* @status - Result passed back from the secondary CPU to
@@ -88,7 +91,7 @@ asmlinkage void secondary_start_kernel(void);
*/
struct secondary_data {
struct task_struct *task;
- long status;
+ union secondary_status status;
cpumask_t cpu_died_early_mask;
};
@@ -123,9 +126,11 @@ static inline void __noreturn cpu_park_loop(void)
}
}
-static inline void update_cpu_boot_status(int val)
+static inline void update_cpu_boot_status(const unsigned int val)
{
- WRITE_ONCE(secondary_data.status, val);
+ BUILD_BUG_ON(val >= CPU_STATUS_FLAGS_MAX);
+
+ WRITE_ONCE(secondary_data.status.flags[val], 1);
/* Ensure the visibility of the status update */
dsb(ishst);
}
diff --git a/arch/arm64/include/asm/topology.h b/arch/arm64/include/asm/topology.h
index b9eaf4ad7085..f1ff9e40b7cd 100644
--- a/arch/arm64/include/asm/topology.h
+++ b/arch/arm64/include/asm/topology.h
@@ -41,4 +41,6 @@ void update_freq_counters_refs(void);
#include <asm-generic/topology.h>
+#define cpu_primary_thread_mask cpu_none_mask
+
#endif /* _ASM_ARM_TOPOLOGY_H */
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 17868b497d7c..031ff1d4fd29 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -393,7 +393,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
@@ -440,14 +439,14 @@ SYM_FUNC_END(set_cpu_boot_mode_flag)
*
* update_early_cpu_boot_status tmp, status
* - Corrupts tmp1, tmp2
- * - Writes 'status' to __early_cpu_boot_status and makes sure
+ * - Writes 1 to the 'status' field of __early_cpu_boot_status and makes sure
* it is committed to memory.
*/
.macro update_early_cpu_boot_status status, tmp1, tmp2
- mov \tmp2, #\status
adr_l \tmp1, __early_cpu_boot_status
- str \tmp2, [\tmp1]
+ mov \tmp2, #1
+ strb w\tmp2, [\tmp1, #\status]
dmb sy
dc ivac, \tmp1 // Invalidate potentially stale cache line
.endm
@@ -495,8 +494,7 @@ SYM_FUNC_START(__cpu_secondary_check52bitva)
b.ge 2f
#endif
- update_early_cpu_boot_status \
- CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
+ update_early_cpu_boot_status EARLY_CPU_STUCK_REASON_52_BIT_VA, x0, x1
1: wfe
wfi
b 1b
@@ -507,8 +505,7 @@ SYM_FUNC_END(__cpu_secondary_check52bitva)
SYM_FUNC_START_LOCAL(__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 | CPU_STUCK_REASON_NO_GRAN, x1, x2
+ update_early_cpu_boot_status EARLY_CPU_STUCK_REASON_NO_GRAN, x1, x2
1:
wfe
wfi
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index fb3a93090a3c..b3023557d789 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -64,7 +64,7 @@
*/
struct secondary_data secondary_data = {};
/* Number of CPUs which aren't online, but looping in kernel text. */
-static int cpus_stuck_in_kernel;
+static bool cpus_stuck_in_kernel;
static int ipi_irq_base __ro_after_init;
static int nr_ipi __ro_after_init = NR_IPI;
@@ -93,6 +93,18 @@ static inline int op_cpu_kill(unsigned int cpu)
}
#endif
+static bool smp_parallel_bringup;
+
+bool arch_cpuhp_init_parallel_bringup(void)
+{
+ const struct cpu_operations *ops = get_secondary_cpu_ops();
+
+ smp_parallel_bringup = ops &&
+ ops->cpu_boot_has_arg &&
+ ops->cpu_boot_has_arg();
+ return smp_parallel_bringup;
+}
+
/*
* Boot a secondary CPU, and assign it the specified idle task.
* This also gives us the initial stack to use for this CPU.
@@ -107,13 +119,11 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
* We need to tell the secondary core where to find its stack and the
* page tables.
*/
- if (ops->cpu_boot_has_arg && ops->cpu_boot_has_arg())
+ if (smp_parallel_bringup)
arg = idle;
else
secondary_data.task = idle;
- update_cpu_boot_status(CPU_MMU_OFF);
-
/* Now bring the CPU into our world */
if (ops->cpu_boot)
ret = ops->cpu_boot(cpu, (unsigned long)arg);
@@ -125,46 +135,42 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
{
- long status;
+ union secondary_status status;
if (is_alive)
return;
- secondary_data.task = NULL;
- status = READ_ONCE(secondary_data.status);
- if (status == CPU_MMU_OFF)
- status = READ_ONCE(__early_cpu_boot_status);
-
- if (cpumask_test_and_clear_cpu(cpu, &secondary_data.cpu_died_early_mask))
- set_cpu_present(cpu, false);
-
/* A CPU has failed to boot. Try to figure out what happened. */
- switch (status & CPU_BOOT_STATUS_MASK) {
- default:
- pr_err("CPU%u: failed in unknown state : 0x%lx\n",
- cpu, status);
- cpus_stuck_in_kernel++;
- break;
- case CPU_KILL_ME:
+ if (smp_parallel_bringup)
+ pr_warn_once("Parallel CPU bringup failed; consider passing \"cpuhp.parallel=off\" for a more accurate diagnosis.\n");
+ else
+ secondary_data.task = NULL;
+
+ status.val = READ_ONCE(__early_cpu_boot_status);
+ if (status.early_flags[EARLY_CPU_STUCK_REASON_52_BIT_VA]) {
+ pr_crit_once("CPU%u detected lack of support for 52-bit VAs\n",
+ cpu);
+ }
+
+ if (status.early_flags[EARLY_CPU_STUCK_REASON_NO_GRAN]) {
+ pr_crit_once("CPU%u detected lack of support for %luK granules\n",
+ cpu, PAGE_SIZE / SZ_1K);
+ }
+
+ status = READ_ONCE(secondary_data.status);
+ if (status.flags[CPU_PANIC_KERNEL])
+ panic("CPU%u detected unsupported configuration\n", cpu);
+
+ if (cpumask_test_and_clear_cpu(cpu, &secondary_data.cpu_died_early_mask)) {
+ set_cpu_present(cpu, false);
if (!op_cpu_kill(cpu)) {
pr_crit("CPU%u: died during early boot\n", cpu);
- break;
+ return;
}
- pr_crit("CPU%u: may not have shut down cleanly\n", cpu);
- fallthrough;
- case CPU_STUCK_IN_KERNEL:
- pr_crit("CPU%u: is stuck in kernel\n", cpu);
- if (status & CPU_STUCK_REASON_52_BIT_VA)
- pr_crit("CPU%u: does not support 52-bit VAs\n", cpu);
- if (status & CPU_STUCK_REASON_NO_GRAN) {
- pr_crit("CPU%u: does not support %luK granule\n",
- cpu, PAGE_SIZE / SZ_1K);
- }
- cpus_stuck_in_kernel++;
- break;
- case CPU_PANIC_KERNEL:
- panic("CPU%u detected unsupported configuration\n", cpu);
}
+
+ pr_crit_once("CPUs may be stuck in kernel\n");
+ cpus_stuck_in_kernel = true;
}
static void init_gic_priority_masking(void)
@@ -409,12 +415,8 @@ void __noreturn cpu_die_early(void)
cpumask_set_cpu(cpu, &secondary_data.cpu_died_early_mask);
- if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
- update_cpu_boot_status(CPU_KILL_ME);
+ if (IS_ENABLED(CONFIG_HOTPLUG_CPU))
__cpu_try_die(cpu);
- }
-
- update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
cpu_park_loop();
}
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 79d90226fd5d..59e572a09304 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -61,7 +61,7 @@ static bool rodata_is_rw __ro_after_init = true;
* The booting CPU updates the failed status @__early_cpu_boot_status,
* with MMU turned off.
*/
-long __section(".mmuoff.data.write") __early_cpu_boot_status;
+long __section(".mmuoff.data.write") __early_cpu_boot_status = 0;
static DEFINE_SPINLOCK(swapper_pgdir_lock);
static DEFINE_MUTEX(fixmap_lock);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 23/23] arm64: smp: Harden parallel CPU bringup against broken PSCI firmware
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
` (21 preceding siblings ...)
2026-10-09 10:07 ` [PATCH v2 22/23] arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining Will Deacon
@ 2026-10-09 10:07 ` Will Deacon
22 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 10:07 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, David Woodhouse, Peter Zijlstra,
Marc Zyngier
Since firmware has occasionally been known to get things wrong, harden
our parallel CPU bringup code against a PSCI implementation that passes
the CPU_ON argument to an incorrect CPU.
The primary CPU writes the MPIDR of the incoming CPU to the penultimate
slot of its task stack and this is then checked against the MPIDR_EL1
register during early kernel entry. A mismatch is reported via the
existing failure reporting mechanism and the CPU is not brought online.
Suggested-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/include/asm/smp.h | 4 +++-
arch/arm64/kernel/asm-offsets.c | 1 +
arch/arm64/kernel/head.S | 39 ++++++++++++++++++++++++++++-----
arch/arm64/kernel/smp.c | 12 ++++++++--
4 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 3decb42164aa..1ecfa4fa4f82 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -15,7 +15,9 @@
/* Offsets for late (i.e. MMU-enabled) CPU boot reasons */
/* Fatal system error detected by secondary CPU, crash the system */
#define CPU_PANIC_KERNEL (0)
-#define CPU_STATUS_FLAGS_MAX (1)
+/* The PSCI v0.2+ implementation passed the wrong argument */
+#define CPU_BROKEN_PSCI_ARG (1)
+#define CPU_STATUS_FLAGS_MAX (2)
#ifndef __ASSEMBLER__
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index 9c853ed3ceab..0cbd10af13ac 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -97,6 +97,7 @@ int main(void)
BLANK();
#endif
DEFINE(CPU_BOOT_TASK, offsetof(struct secondary_data, task));
+ DEFINE(CPU_BOOT_STATUS_FLAGS, offsetof(struct secondary_data, status.flags));
BLANK();
DEFINE(FTR_OVR_VAL_OFFSET, offsetof(struct arm64_ftr_override, val));
DEFINE(FTR_OVR_MASK_OFFSET, offsetof(struct arm64_ftr_override, mask));
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 031ff1d4fd29..3f005e226547 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -192,11 +192,21 @@ SYM_CODE_END(preserve_boot_args)
* its location in the task stack. We reserve the entire pt_regs space
* for consistency with user tasks and kthreads.
*/
- .macro init_cpu_task tsk, tmp1, tmp2
+ .macro init_cpu_task tsk, tmp1, tmp2, check_mpidr=
msr sp_el0, \tsk
ldr \tmp1, [\tsk, #TSK_STACK]
- add sp, \tmp1, #THREAD_SIZE
+ mov sp, \tmp1
+ .ifnb \check_mpidr
+ mov_q \tmp1, MPIDR_HWID_BITMASK
+ mrs \tmp2, mpidr_el1
+ and \tmp2, \tmp2, \tmp1
+ ldr \tmp1, [sp, #8]
+ sub \tmp1, \tmp1, \tmp2
+ cbnz \tmp1, __cpu_secondary_broken_psci_arg
+ .endif
+
+ add sp, sp, #THREAD_SIZE
sub sp, sp, #PT_REGS_SIZE
stp xzr, xzr, [sp, #S_STACKFRAME]
@@ -397,15 +407,16 @@ SYM_FUNC_START_LOCAL(__secondary_switched)
msr vbar_el1, x5
isb
+ cbz x19, 1f
mov x2, x19
- cbnz x2, 1f
+ init_cpu_task x2, x1, x3, check_mpidr=1
+ b 2f
+1:
adr_l x0, secondary_data
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slow
-
-1:
init_cpu_task x2, x1, x3
-
+2:
#ifdef CONFIG_ARM64_PTR_AUTH
ptrauth_keys_init_cpu x2, x3, x4, x5
#endif
@@ -451,6 +462,22 @@ SYM_FUNC_END(set_cpu_boot_mode_flag)
dc ivac, \tmp1 // Invalidate potentially stale cache line
.endm
+ .macro update_cpu_boot_status status, tmp1, tmp2
+ adr_l \tmp1, secondary_data
+ add \tmp1, \tmp1, #CPU_BOOT_STATUS_FLAGS + \status
+ mov \tmp2, #1
+ strb w\tmp2, [\tmp1]
+ .endm
+
+SYM_FUNC_START_LOCAL(__cpu_secondary_broken_psci_arg)
+ update_cpu_boot_status CPU_BROKEN_PSCI_ARG, x0, x1
+1:
+ wfe
+ wfi
+ b 1b
+SYM_FUNC_END(__cpu_secondary_broken_psci_arg)
+
+
/*
* Enable the MMU.
*
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index b3023557d789..8dc29bdef1a1 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -119,10 +119,13 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *idle)
* We need to tell the secondary core where to find its stack and the
* page tables.
*/
- if (smp_parallel_bringup)
+ if (smp_parallel_bringup) {
+ /* Avoid clobbering STACK_END_MAGIC */
+ *(end_of_stack(idle) + 1) = cpu_logical_map(cpu);
arg = idle;
- else
+ } else {
secondary_data.task = idle;
+ }
/* Now bring the CPU into our world */
if (ops->cpu_boot)
@@ -158,6 +161,11 @@ void arch_cpuhp_cleanup_kick_cpu(unsigned int cpu, bool is_alive)
}
status = READ_ONCE(secondary_data.status);
+ if (status.flags[CPU_BROKEN_PSCI_ARG]) {
+ pr_crit_once("CPU%u detected broken PSCI v0.2+ CPU_ON argument passing\n",
+ cpu);
+ }
+
if (status.flags[CPU_PANIC_KERNEL])
panic("CPU%u detected unsupported configuration\n", cpu);
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op
2026-10-09 10:07 ` [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op Will Deacon
@ 2026-10-09 10:46 ` David Woodhouse
2026-10-09 11:07 ` Will Deacon
0 siblings, 1 reply; 28+ messages in thread
From: David Woodhouse @ 2026-10-09 10:46 UTC (permalink / raw)
To: Will Deacon, linux-arm-kernel
Cc: linux-kernel, Thomas Gleixner, Ben Horgan, Ștefania Ion,
Catalin Marinas, Pankaj Patil, Borislav Petkov,
Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland, Tarun Sahu,
Fuad Tabba, Peter Zijlstra, Marc Zyngier
[-- Attachment #1: Type: text/plain, Size: 241 bytes --]
On Fri, 2026-10-09 at 11:07 +0100, Will Deacon wrote:
> Change cpuhp_ap_sync_alive() to poll using atomic_cond_read_relaxed()
relaxed.
...
> + atomic_cond_read_acquire(st, VAL == SYNC_STATE_SHOULD_ONLINE);
acquire.
[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 6179 bytes --]
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op
2026-10-09 10:46 ` David Woodhouse
@ 2026-10-09 11:07 ` Will Deacon
0 siblings, 0 replies; 28+ messages in thread
From: Will Deacon @ 2026-10-09 11:07 UTC (permalink / raw)
To: David Woodhouse
Cc: linux-arm-kernel, linux-kernel, Thomas Gleixner, Ben Horgan,
Ștefania Ion, Catalin Marinas, Pankaj Patil,
Borislav Petkov, Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland,
Tarun Sahu, Fuad Tabba, Peter Zijlstra, Marc Zyngier
On Fri, Oct 09, 2026 at 11:46:11AM +0100, David Woodhouse wrote:
> On Fri, 2026-10-09 at 11:07 +0100, Will Deacon wrote:
> > Change cpuhp_ap_sync_alive() to poll using atomic_cond_read_relaxed()
>
> relaxed.
>
> ...
>
> > + atomic_cond_read_acquire(st, VAL == SYNC_STATE_SHOULD_ONLINE);
>
> acquire.
Oops, I'll fix the commit message. Thanks. I think we need acquire
semantics here so that the AP sees anything published by the BP prior to
advancing the state.
Will
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()
2026-10-09 10:07 ` [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu() Will Deacon
@ 2026-10-09 15:44 ` Ben Horgan
0 siblings, 0 replies; 28+ messages in thread
From: Ben Horgan @ 2026-10-09 15:44 UTC (permalink / raw)
To: Will Deacon, linux-arm-kernel
Cc: linux-kernel, Thomas Gleixner, Ștefania Ion,
Catalin Marinas, Pankaj Patil, Borislav Petkov,
Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland, Tarun Sahu,
Fuad Tabba, David Woodhouse, Peter Zijlstra, Marc Zyngier
Hi Will,
On 10/9/26 11:07, Will Deacon wrote:
> From: Fuad Tabba <fuad.tabba@linux.dev>
>
> MPAMIDR_EL1 is the one ID register __cpuinfo_store_cpu() doesn't read.
> Its read was deferred to init_cpu_features() and update_cpu_features()
> because it traps on firmware that fails to enable MPAM. Only the
> sanitised ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 carried the arm64.nompam
> override for such firmware.
>
> Store ID_AA64PFR0_EL1 through read_cpuid_with_overrides() as well. Then
> read MPAMIDR_EL1 in __cpuinfo_store_cpu() again, beside GMID_EL1, gated
> on this CPU's overridden ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1. cpu_data
> is then complete before init_cpu_features() runs.
>
> Other readers of the stored ID_AA64PFR0_EL1 see the override too. With
> arm64.no32bit_el0, __cpuinfo_store_cpu() no longer reads the AArch32 ID
> registers. Adding allow_mismatched_32bit_el0 no longer enables 32-bit
> EL0 either.
>
> detect_ftr_has_mpam(), a system-wide test on the sanitised values,
> stays for the ARM64_MPAM capability.
>
> Suggested-by: Will Deacon <will@kernel.org>
> Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
> Signed-off-by: Will Deacon <will@kernel.org>
> ---> arch/arm64/kernel/cpufeature.c | 10 +++-------
> arch/arm64/kernel/cpuinfo.c | 11 ++++-------
> 2 files changed, 7 insertions(+), 14 deletions(-)
>
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index 3161531ad401..9212f10c1d1a 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -1248,10 +1248,8 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info)
> cpacr_restore(cpacr);
> }
>
> - if (detect_ftr_has_mpam()) {
> - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
> + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
> init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr);
info->reg_mpamidr isn't useful if we aren't going to use MPAM so I
suppose all the info->reg_mpamidr accesses should be additionally
guarded with IS_ENABLED(CONFIG_ARM64_MPAM) as already done for MTE.
That's a separate change though and what you've got here looks good to
me. So, FWIW,
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Thanks,
Ben
> - }
>
> if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
> init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid);
> @@ -1504,11 +1502,9 @@ void update_cpu_features(int cpu,
> cpacr_restore(cpacr);
> }
>
> - if (detect_ftr_has_mpam()) {
> - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
> + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
> taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu,
> - info->reg_mpamidr, boot->reg_mpamidr);
> - }
> + info->reg_mpamidr, boot->reg_mpamidr);
>
> /*
> * The kernel uses the LDGM/STGM instructions and the number of tags
> diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
> index d48167fe4218..0ae40b0c7b2f 100644
> --- a/arch/arm64/kernel/cpuinfo.c
> +++ b/arch/arm64/kernel/cpuinfo.c
> @@ -495,7 +495,7 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
> info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
> info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1);
> info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1);
> - info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1);
> + info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1);
> info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1);
> info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1);
> info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
> @@ -505,15 +505,12 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
> if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
> info->reg_gmid = read_cpuid(GMID_EL1);
>
> + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
> + info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
> +
> if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
> __cpuinfo_store_cpu_32bit(&info->aarch32);
>
> - /*
> - * info->reg_mpamidr deferred to {init,update}_cpu_features because we
> - * don't want to read it (and trigger a trap on buggy firmware) if
> - * using an aa64pfr0_el1 override to unconditionally disable MPAM.
> - */
> -
> if (IS_ENABLED(CONFIG_ARM64_SME) &&
> id_aa64pfr1_sme(info->reg_id_aa64pfr1)) {
> /*
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied
2026-10-09 10:07 ` [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied Will Deacon
@ 2026-10-09 15:51 ` Ben Horgan
0 siblings, 0 replies; 28+ messages in thread
From: Ben Horgan @ 2026-10-09 15:51 UTC (permalink / raw)
To: Will Deacon, linux-arm-kernel
Cc: linux-kernel, Thomas Gleixner, Ștefania Ion,
Catalin Marinas, Pankaj Patil, Borislav Petkov,
Lorenzo Pieralisi, Jinjie Ruan, Mark Rutland, Tarun Sahu,
Fuad Tabba, David Woodhouse, Peter Zijlstra, Marc Zyngier
Hi Will,
On 10/9/26 11:07, Will Deacon wrote:
> From: Fuad Tabba <fuad.tabba@linux.dev>
>
> __cpuinfo_store_cpu() stores ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 with
> their overrides applied and every other register raw. Store the rest
> the same way. Everything read with read_cpuid() goes through
> read_cpuid_with_overrides(). CTR_EL0 and CNTFRQ_EL0, which are read
> otherwise, go through arm64_ftr_reg_override(). It returns a register
> with no override unchanged, so an override added to one later takes
> effect without a change here.
>
> init_cpu_ftr_reg() then gets values that already carry every valid
> override, so its code to apply one is dead. Remove that code.
> init_cpu_ftr_reg() still removes an unsafe override, so the same
> overrides are rejected and sys_val is unchanged. Only its log changes.
> The raw value is gone, so it logs an override equal to that value as
> "forced" rather than "already set".
>
> Secondaries are compared with the boot CPU on their stored values, so
> CPUs that differ only in a field the command line turned off no longer
> taint the kernel.
>
> Suggested-by: Will Deacon <will@kernel.org>
> Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
> Signed-off-by: Will Deacon <will@kernel.org>
> ---
> arch/arm64/kernel/cpufeature.c | 17 +++----
> arch/arm64/kernel/cpuinfo.c | 82 +++++++++++++++++-----------------
> 2 files changed, 46 insertions(+), 53 deletions(-)
>
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index 9212f10c1d1a..d69a5b8304b6 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -1043,6 +1043,8 @@ void __init validate_ftr_regs(void)
> * Also initialises the strict_mask for the register.
> * Any bits that are not covered by an arm64_ftr_bits entry are considered
> * RES0 for the system-wide value, and must strictly match.
> + * @new has been through arm64_ftr_reg_override(), so a valid override is
> + * already applied to it. Only an unsafe one is left to remove here.
> */
> static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
> {
> @@ -1063,29 +1065,20 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
> s64 ftr_ovr = arm64_ftr_value(ftrp, reg->override->val);
>
> if ((ftr_mask & reg->override->mask) == ftr_mask) {
> - s64 tmp = arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new);
> - char *str = NULL;
> + char *str = "forced";
>
> - if (ftr_ovr != tmp) {
> + if (arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new) != ftr_ovr) {
> /* Unsafe, remove the override */
> reg->override->mask &= ~ftr_mask;
> reg->override->val &= ~ftr_mask;
> - tmp = ftr_ovr;
> str = "ignoring override";
> - } else if (ftr_new != tmp) {
> - /* Override was valid */
> - ftr_new = tmp;
> - str = "forced";
> - } else {
> - /* Override was the safe value */
> - str = "already set";
> }
>
> pr_warn("%s[%d:%d]: %s to %llx\n",
> reg->name,
> ftrp->shift + ftrp->width - 1,
> ftrp->shift, str,
> - tmp & (BIT(ftrp->width) - 1));
> + ftr_ovr & (BIT(ftrp->width) - 1));
> } else if ((ftr_mask & reg->override->val) == ftr_mask) {
> reg->override->val &= ~ftr_mask;
> pr_warn("%s[%d:%d]: impossible override, ignored\n",
> diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
> index 0ae40b0c7b2f..1b92e52989b1 100644
> --- a/arch/arm64/kernel/cpuinfo.c
> +++ b/arch/arm64/kernel/cpuinfo.c
> @@ -443,33 +443,33 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
>
> static void __cpuinfo_store_cpu_32bit(struct cpuinfo_32bit *info)
> {
> - info->reg_id_dfr0 = read_cpuid(ID_DFR0_EL1);
> - info->reg_id_dfr1 = read_cpuid(ID_DFR1_EL1);
> - info->reg_id_isar0 = read_cpuid(ID_ISAR0_EL1);
> - info->reg_id_isar1 = read_cpuid(ID_ISAR1_EL1);
> - info->reg_id_isar2 = read_cpuid(ID_ISAR2_EL1);
> - info->reg_id_isar3 = read_cpuid(ID_ISAR3_EL1);
> - info->reg_id_isar4 = read_cpuid(ID_ISAR4_EL1);
> - info->reg_id_isar5 = read_cpuid(ID_ISAR5_EL1);
> - info->reg_id_isar6 = read_cpuid(ID_ISAR6_EL1);
> - info->reg_id_mmfr0 = read_cpuid(ID_MMFR0_EL1);
> - info->reg_id_mmfr1 = read_cpuid(ID_MMFR1_EL1);
> - info->reg_id_mmfr2 = read_cpuid(ID_MMFR2_EL1);
> - info->reg_id_mmfr3 = read_cpuid(ID_MMFR3_EL1);
> - info->reg_id_mmfr4 = read_cpuid(ID_MMFR4_EL1);
> - info->reg_id_mmfr5 = read_cpuid(ID_MMFR5_EL1);
> - info->reg_id_pfr0 = read_cpuid(ID_PFR0_EL1);
> - info->reg_id_pfr1 = read_cpuid(ID_PFR1_EL1);
> - info->reg_id_pfr2 = read_cpuid(ID_PFR2_EL1);
> + info->reg_id_dfr0 = read_cpuid_with_overrides(ID_DFR0_EL1);
> + info->reg_id_dfr1 = read_cpuid_with_overrides(ID_DFR1_EL1);
> + info->reg_id_isar0 = read_cpuid_with_overrides(ID_ISAR0_EL1);
> + info->reg_id_isar1 = read_cpuid_with_overrides(ID_ISAR1_EL1);
> + info->reg_id_isar2 = read_cpuid_with_overrides(ID_ISAR2_EL1);
> + info->reg_id_isar3 = read_cpuid_with_overrides(ID_ISAR3_EL1);
> + info->reg_id_isar4 = read_cpuid_with_overrides(ID_ISAR4_EL1);
> + info->reg_id_isar5 = read_cpuid_with_overrides(ID_ISAR5_EL1);
> + info->reg_id_isar6 = read_cpuid_with_overrides(ID_ISAR6_EL1);
> + info->reg_id_mmfr0 = read_cpuid_with_overrides(ID_MMFR0_EL1);
> + info->reg_id_mmfr1 = read_cpuid_with_overrides(ID_MMFR1_EL1);
> + info->reg_id_mmfr2 = read_cpuid_with_overrides(ID_MMFR2_EL1);
> + info->reg_id_mmfr3 = read_cpuid_with_overrides(ID_MMFR3_EL1);
> + info->reg_id_mmfr4 = read_cpuid_with_overrides(ID_MMFR4_EL1);
> + info->reg_id_mmfr5 = read_cpuid_with_overrides(ID_MMFR5_EL1);
> + info->reg_id_pfr0 = read_cpuid_with_overrides(ID_PFR0_EL1);
> + info->reg_id_pfr1 = read_cpuid_with_overrides(ID_PFR1_EL1);
> + info->reg_id_pfr2 = read_cpuid_with_overrides(ID_PFR2_EL1);
>
> - info->reg_mvfr0 = read_cpuid(MVFR0_EL1);
> - info->reg_mvfr1 = read_cpuid(MVFR1_EL1);
> - info->reg_mvfr2 = read_cpuid(MVFR2_EL1);
> + info->reg_mvfr0 = read_cpuid_with_overrides(MVFR0_EL1);
> + info->reg_mvfr1 = read_cpuid_with_overrides(MVFR1_EL1);
> + info->reg_mvfr2 = read_cpuid_with_overrides(MVFR2_EL1);
> }
>
> static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
> {
> - info->reg_cntfrq = arch_timer_get_cntfrq();
> + info->reg_cntfrq = arm64_ftr_reg_override(SYS_CNTFRQ_EL0, arch_timer_get_cntfrq());
> /*
> * Use the effective value of the CTR_EL0 than the raw value
> * exposed by the CPU. CTR_EL0.IDC field value must be interpreted
> @@ -478,35 +478,35 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
> * effective value of the CTR_EL0 in our internal records for
> * accurate sanity check and feature enablement.
> */
> - info->reg_ctr = read_cpuid_effective_cachetype();
> - info->reg_dczid = read_cpuid(DCZID_EL0);
> + info->reg_ctr = arm64_ftr_reg_override(SYS_CTR_EL0, read_cpuid_effective_cachetype());
> + info->reg_dczid = read_cpuid_with_overrides(DCZID_EL0);
> info->reg_midr = read_cpuid_id();
> info->reg_revidr = read_cpuid(REVIDR_EL1);
> info->reg_aidr = read_cpuid(AIDR_EL1);
>
> - info->reg_id_aa64dfr0 = read_cpuid(ID_AA64DFR0_EL1);
> - info->reg_id_aa64dfr1 = read_cpuid(ID_AA64DFR1_EL1);
> - info->reg_id_aa64isar0 = read_cpuid(ID_AA64ISAR0_EL1);
> - info->reg_id_aa64isar1 = read_cpuid(ID_AA64ISAR1_EL1);
> - info->reg_id_aa64isar2 = read_cpuid(ID_AA64ISAR2_EL1);
> - info->reg_id_aa64isar3 = read_cpuid(ID_AA64ISAR3_EL1);
> - info->reg_id_aa64mmfr0 = read_cpuid(ID_AA64MMFR0_EL1);
> - info->reg_id_aa64mmfr1 = read_cpuid(ID_AA64MMFR1_EL1);
> - info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1);
> - info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1);
> - info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1);
> + info->reg_id_aa64dfr0 = read_cpuid_with_overrides(ID_AA64DFR0_EL1);
> + info->reg_id_aa64dfr1 = read_cpuid_with_overrides(ID_AA64DFR1_EL1);
> + info->reg_id_aa64isar0 = read_cpuid_with_overrides(ID_AA64ISAR0_EL1);
> + info->reg_id_aa64isar1 = read_cpuid_with_overrides(ID_AA64ISAR1_EL1);
> + info->reg_id_aa64isar2 = read_cpuid_with_overrides(ID_AA64ISAR2_EL1);
> + info->reg_id_aa64isar3 = read_cpuid_with_overrides(ID_AA64ISAR3_EL1);
> + info->reg_id_aa64mmfr0 = read_cpuid_with_overrides(ID_AA64MMFR0_EL1);
> + info->reg_id_aa64mmfr1 = read_cpuid_with_overrides(ID_AA64MMFR1_EL1);
> + info->reg_id_aa64mmfr2 = read_cpuid_with_overrides(ID_AA64MMFR2_EL1);
> + info->reg_id_aa64mmfr3 = read_cpuid_with_overrides(ID_AA64MMFR3_EL1);
> + info->reg_id_aa64mmfr4 = read_cpuid_with_overrides(ID_AA64MMFR4_EL1);
> info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1);
> info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1);
> - info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1);
> - info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
> - info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1);
> - info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1);
> + info->reg_id_aa64pfr2 = read_cpuid_with_overrides(ID_AA64PFR2_EL1);
> + info->reg_id_aa64zfr0 = read_cpuid_with_overrides(ID_AA64ZFR0_EL1);
> + info->reg_id_aa64smfr0 = read_cpuid_with_overrides(ID_AA64SMFR0_EL1);
> + info->reg_id_aa64fpfr0 = read_cpuid_with_overrides(ID_AA64FPFR0_EL1);
>
> if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1))
> - info->reg_gmid = read_cpuid(GMID_EL1);
> + info->reg_gmid = read_cpuid_with_overrides(GMID_EL1);
>
> if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1))
> - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1);
> + info->reg_mpamidr = read_cpuid_with_overrides(MPAMIDR_EL1);
>
> if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
> __cpuinfo_store_cpu_32bit(&info->aarch32);
For completeness shouldn't read_cpuid(SMIDR_EL1) change to
read_cpuid_with_overrides(SMIDR_EL1)?
Thanks,
Ben
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-10-09 15:51 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-09 10:07 [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+ Will Deacon
2026-10-09 10:07 ` [PATCH v2 01/23] cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap() Will Deacon
2026-10-09 10:07 ` [PATCH v2 02/23] cpu/hotplug: Avoid trying to bring up CPUs that are already online Will Deacon
2026-10-09 10:07 ` [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op Will Deacon
2026-10-09 10:46 ` David Woodhouse
2026-10-09 11:07 ` Will Deacon
2026-10-09 10:07 ` [PATCH v2 04/23] cpu/hotplug: Propagate bring-up status to arch_cpuhp_cleanup_kick_cpu() Will Deacon
2026-10-09 10:07 ` [PATCH v2 05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store Will Deacon
2026-10-09 10:07 ` [PATCH v2 06/23] arm64: cpufeature: Add read_cpuid_with_overrides() Will Deacon
2026-10-09 10:07 ` [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu() Will Deacon
2026-10-09 15:44 ` Ben Horgan
2026-10-09 10:07 ` [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied Will Deacon
2026-10-09 15:51 ` Ben Horgan
2026-10-09 10:07 ` [PATCH v2 09/23] arm64: smp: Tidy up smp_prepare_cpus() Will Deacon
2026-10-09 10:07 ` [PATCH v2 10/23] arm64: smp: Tidy up cpuinfo init and cpufeature updates Will Deacon
2026-10-09 10:07 ` [PATCH v2 11/23] arm64: smp: Defer update of secondary CPU capabilities Will Deacon
2026-10-09 10:07 ` [PATCH v2 12/23] arm64: smp: Don't bother printing the I-cache policy for each CPU Will Deacon
2026-10-09 10:07 ` [PATCH v2 13/23] arm64: smp: Defer RCU registration during secondary CPU bringup Will Deacon
2026-10-09 10:07 ` [PATCH v2 14/23] arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU onlining Will Deacon
2026-10-09 10:07 ` [PATCH v2 15/23] arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP " Will Deacon
2026-10-09 10:07 ` [PATCH v2 16/23] arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu Will Deacon
2026-10-09 10:07 ` [PATCH v2 17/23] arm64: cpu_ops: Introduce get_secondary_cpu_ops() Will Deacon
2026-10-09 10:07 ` [PATCH v2 18/23] firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs Will Deacon
2026-10-09 10:07 ` [PATCH v2 19/23] firmware/psci: Extend ->cpu_on() callback to take an additional argument Will Deacon
2026-10-09 10:07 ` [PATCH v2 20/23] arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot() Will Deacon
2026-10-09 10:07 ` [PATCH v2 21/23] arm64: smp: Pass secondary CPU boot parameters via firmware if possible Will Deacon
2026-10-09 10:07 ` [PATCH v2 22/23] arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining Will Deacon
2026-10-09 10:07 ` [PATCH v2 23/23] arm64: smp: Harden parallel CPU bringup against broken PSCI firmware Will Deacon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®