Skip to contentSkip to conversation

arm64: Implement parallel CPU onlining with PSCI v0.2+

PATCH In review Will Deacon 8 participants 35 messages Oct 9 – Oct 15

Subsystems (6)
Arm (32-bit) x86 arm64 RCU +2 more subsystems
lore
Share & tools
lore

Will Deacon

author maintainer
#0 lore raw

v2 cover arm64: Implement parallel CPU onlining with PSCI v0.2+

Hi everyone,

This is version two of the patches I previously posted here:

https://lore.kernel.org/all/[email protected]/

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 <[email protected]>
Cc Ben Horgan <[email protected]>
Cc Ștefania Ion <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Pankaj Patil <[email protected]>
Cc Borislav Petkov <[email protected]>
Cc Lorenzo Pieralisi <[email protected]>
Cc Jinjie Ruan <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Tarun Sahu <[email protected]>
Cc Fuad Tabba <[email protected]>
Cc David Woodhouse <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Marc Zyngier <[email protected]>

--->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(-)
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#1 lore raw

PATCH v2 1/23 cpu/hotplug: Clean up cmpxchg() logic in cpuhp_can_boot_ap()

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.

---
 kernel/cpu.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
kernel/cpu.c +4 -4
index b3c8553d7bd6..198c929c452a 100644
@@ -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;
 }
 
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#2 lore raw

PATCH v2 2/23 cpu/hotplug: Avoid trying to bring up CPUs that are already online

In reply to Will Deacon

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().

---
 kernel/cpu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
kernel/cpu.c +2 -1
index 198c929c452a..97a9bfe4edad 100644
@@ -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
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
maintainer
#3 lore raw

Re: [PATCH v2 02/23] cpu/hotplug: Avoid trying to bring up CPUs that are already online

Quoted 4 lines · Will Deacon

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.

s/we can ../the call can be .../

Quoted 2 lines

Check that the target CPU isn't already online before invoking cpu_up() from cpuhp_bringup_mask().

Confused.

cpuhp_bringup_mask() is only invoked from bringup_nonboot_cpus() during init. So there is only _ONE_ CPU which can be online and in the present mask, i.e. the boot CPU, right? Any other CPU than that being online would be a bug, no?

Can you spell that out please and add a comment to that effect?

I'm not opposed to the change per se, but I doubt that the "win" is actually measurable as you trade that one pointless call against a conditional which has to be executed NR_CPUS times, where NR_CPUS-1 times the conditinal is pointless. There is ZERO contention at that point, so what's the actual benefit?

Thanks,

tglx

author maintainer
#4 lore raw

PATCH v2 3/23 cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op

In reply to Will Deacon

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.

---
 kernel/cpu.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
kernel/cpu.c +1 -2
index 97a9bfe4edad..786c3191431c 100644
@@ -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)
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
#5 lore raw

Re: [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op

Quoted 1 line · Will Deacon

Change cpuhp_ap_sync_alive() to poll using atomic_cond_read_relaxed()

relaxed.

...

Quoted 1 line

+       atomic_cond_read_acquire(st, VAL == SYNC_STATE_SHOULD_ONLINE);

acquire.

author maintainer
#6 lore raw

Re: [PATCH v2 03/23] cpu/hotplug: Avoid busy-polling on archs where cpu_relax() is a no-op

Quoted 10 lines · David Woodhouse
Quoted 1 line · Will Deacon

Change cpuhp_ap_sync_alive() to poll using atomic_cond_read_relaxed()

relaxed.

...

Quoted 1 line

+       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

author maintainer
#7 lore raw

PATCH v2 4/23 cpu/hotplug: Propagate bring-up status to arch_cpuhp_cleanup_kick_cpu()

In reply to Will Deacon

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.

---
 arch/x86/kernel/smpboot.c  | 4 ++--
 include/linux/cpuhotplug.h | 2 +-
 kernel/cpu.c               | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)
arch/x86/kernel/smpboot.c +2 -2
index ba01a9e919b7..f10c64504244 100644
@@ -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)
include/linux/cpuhotplug.h +1 -1
index feb32949aeea..cbdcf1659d90 100644
@@ -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);
 
kernel/cpu.c +2 -2
index 786c3191431c..df3b6bc4c86b 100644
@@ -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 */
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#8 lore raw

PATCH v2 5/23 arm64: cpufeature: Check arm64_ftr_regs[] before the first store

In reply to Will Deacon

From: Fuad Tabba <[email protected]>

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().

---
 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(-)
arch/arm64/include/asm/cpu.h +1 -0
index 3c008821219c..f332fbd8e46d 100644
@@ -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);
arch/arm64/kernel/cpufeature.c +1 -4
index 32102c3912fa..727e1665c987 100644
@@ -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);
arch/arm64/kernel/smp.c +2 -0
index a61dc3016a11..eac217b9ebf1 100644
@@ -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();
 
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
Reviewed-by on v2 5/23
#9 lore raw

Re: [PATCH v2 05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store

Quoted 5 lines · Will Deacon

From: Fuad Tabba <[email protected]>

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

It also checks whether the register field extends beyond 64 bits

Quoted 56 lines

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().

---
 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(-)
arch/arm64/include/asm/cpu.h +1 -0
index 3c008821219c..f332fbd8e46d 100644
@@ -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);
arch/arm64/kernel/cpufeature.c +1 -4
index 32102c3912fa..727e1665c987 100644
@@ -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);
arch/arm64/kernel/smp.c +0 -0
index a61dc3016a11..eac217b9ebf1 100644

arch/arm64/kernel/smp.c void __init smp_prepare_boot_cpu(void) · line 459

Quoted 5 lines
arch/arm64/kernel/smp.c +2 -0
@@ -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();
Quoted 3 lines
arch/arm64/kernel/smp.c +0 -0
 	cpuinfo_store_boot_cpu();
 	setup_boot_cpu_features();
 
Signature · 2 lines
Best regards,
Jinjie
author maintainer
#10 lore raw

PATCH v2 6/23 arm64: cpufeature: Add read_cpuid_with_overrides()

In reply to Will Deacon

From: Fuad Tabba <[email protected]>

__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.

---
 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(-)
arch/arm64/include/asm/cpu.h +0 -1
index f332fbd8e46d..6d2a5ac4c2f8 100644
@@ -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 */
arch/arm64/include/asm/cpufeature.h +11 -0
index 4f04ad82ea34..5a4d25325856 100644
@@ -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;
arch/arm64/kernel/cpufeature.c +28 -29

81 lines not on this page · raw message

arch/arm64/kernel/cpuinfo.c +2 -2
index 45c63f3d75c5..d48167fe4218 100644
@@ -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))
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#11 lore raw

PATCH v2 7/23 arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()

In reply to Will Deacon

From: Fuad Tabba <[email protected]>

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.

---
 arch/arm64/kernel/cpufeature.c | 10 +++-------
 arch/arm64/kernel/cpuinfo.c    | 11 ++++-------
 2 files changed, 7 insertions(+), 14 deletions(-)
arch/arm64/kernel/cpufeature.c +3 -7
index 3161531ad401..9212f10c1d1a 100644
@@ -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
arch/arm64/kernel/cpuinfo.c +4 -7
index d48167fe4218..0ae40b0c7b2f 100644
@@ -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)) {
 		/*
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
Reviewed-by on v2 7/23
#12 lore raw

Re: [PATCH v2 07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()

Hi Will,

Quoted 33 lines · Will Deacon

From: Fuad Tabba <[email protected]>

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.

---> arch/arm64/kernel/cpufeature.c | 10 +++-------

 arch/arm64/kernel/cpuinfo.c    | 11 ++++-------
 2 files changed, 7 insertions(+), 14 deletions(-)
arch/arm64/kernel/cpufeature.c +0 -0
index 3161531ad401..9212f10c1d1a 100644

arch/arm64/kernel/cpufeature.c void __init init_cpu_features(struct cpuinfo_arm64 *info) · line 1252

Quoted 7 lines
arch/arm64/kernel/cpufeature.c +1 -2
@@ -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,

Thanks,

Ben

Quoted 50 lines
arch/arm64/kernel/cpufeature.c +2 -5
-	}
 
 	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
arch/arm64/kernel/cpuinfo.c +4 -7
index d48167fe4218..0ae40b0c7b2f 100644
@@ -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)) {
 		/*
author maintainer
#13 lore raw

PATCH v2 8/23 arm64: cpufeature: Store every ID register with its overrides applied

In reply to Will Deacon

From: Fuad Tabba <[email protected]>

__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.

---
 arch/arm64/kernel/cpufeature.c | 17 +++----
 arch/arm64/kernel/cpuinfo.c    | 82 +++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 53 deletions(-)
arch/arm64/kernel/cpufeature.c +5 -12
index 9212f10c1d1a..d69a5b8304b6 100644
@@ -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",
arch/arm64/kernel/cpuinfo.c +41 -41

109 lines not on this page · raw message

Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
#14 lore raw

Re: [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied

Hi Will,

Quoted 180 lines · Will Deacon

From: Fuad Tabba <[email protected]>

__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.

---
 arch/arm64/kernel/cpufeature.c | 17 +++----
 arch/arm64/kernel/cpuinfo.c    | 82 +++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 53 deletions(-)
arch/arm64/kernel/cpufeature.c +5 -12
index 9212f10c1d1a..d69a5b8304b6 100644
@@ -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",
arch/arm64/kernel/cpuinfo.c +39 -39
index 0ae40b0c7b2f..1b92e52989b1 100644
@@ -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);
 

arch/arm64/kernel/cpuinfo.c static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) · line 512

Quoted 10 lines
arch/arm64/kernel/cpuinfo.c +2 -2
 	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

#15 lore raw

Re: [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied

Hi Ben,

On Fri, 09 Oct 2026 16:51:14 +0100, Ben Horgan <[email protected]> wrote:
[...]

Quoted 2 lines
Quoted 2 lines

the same way. Everything read with read_cpuid() goes through read_cpuid_with_overrides(). CTR_EL0 and CNTFRQ_EL0, which are read

[...]

Quoted 2 lines

For completeness shouldn't read_cpuid(SMIDR_EL1) change to read_cpuid_with_overrides(SMIDR_EL1)?

"Everything" should have said every register in arm64_ftr_regs[], and SMIDR_EL1 isn't one of them, so read_cpuid_with_overrides() would hit the WARN_ON() in get_arm64_ftr_reg() whenever SME is enabled.

Thanks,
/fuad

#16 lore raw

Re: [PATCH v2 08/23] arm64: cpufeature: Store every ID register with its overrides applied

Hi Fuad,

Quoted 6 lines · Fuad Tabba

Hi Ben,

On Fri, 09 Oct 2026 16:51:14 +0100, Ben Horgan <[email protected]> wrote:
[...]

Quoted 2 lines
Quoted 2 lines

the same way. Everything read with read_cpuid() goes through read_cpuid_with_overrides(). CTR_EL0 and CNTFRQ_EL0, which are read

Quoted 7 lines

[...]

Quoted 2 lines

For completeness shouldn't read_cpuid(SMIDR_EL1) change to read_cpuid_with_overrides(SMIDR_EL1)?

"Everything" should have said every register in arm64_ftr_regs[], and SMIDR_EL1 isn't one of them, so read_cpuid_with_overrides() would hit the WARN_ON() in get_arm64_ftr_reg() whenever SME is enabled.

Thanks for the explanation. If I understand correctly, SMIDR_EL1 isn't in arm64_ftr_regs[] because there is no need to sanitize SMIDR_EL1 to give a consistent view across cpus. If and when support for sme priority is added then a unified view of the SMIDR_EL1.HIP, highest implement priority, would be needed and so at that point deserve a place in arm64_ftr_regs[].

Cheers,
Ben

Quoted 3 lines

Thanks,
/fuad

author maintainer
#17 lore raw

PATCH v2 9/23 arm64: smp: Tidy up smp_prepare_cpus()

In reply to Will Deacon

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.

---
 arch/arm64/kernel/smp.c | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)
arch/arm64/kernel/smp.c +5 -8
index eac217b9ebf1..d2aa949e33a3 100644
@@ -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);
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#18 lore raw

PATCH v2 10/23 arm64: smp: Tidy up cpuinfo init and cpufeature updates

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/include/asm/cpu.h +1 -4
index 6d2a5ac4c2f8..3d1693aaf4e0 100644
@@ -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 */
arch/arm64/kernel/cpufeature.c +17 -4
index d69a5b8304b6..5db456884ffa 100644
@@ -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.
arch/arm64/kernel/cpuinfo.c +0 -11
index 1b92e52989b1..dfb21bf5541d 100644
@@ -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);
 }
arch/arm64/kernel/smp.c +2 -1
index d2aa949e33a3..e1b15cab9ed6 100644
@@ -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 */
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#19 lore raw

PATCH v2 11/23 arm64: smp: Defer update of secondary CPU capabilities

In reply to Will Deacon

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.

---
 arch/arm64/kernel/cpufeature.c | 307 +++++++++++++++++----------------
 1 file changed, 155 insertions(+), 152 deletions(-)
arch/arm64/kernel/cpufeature.c +155 -152
index 5db456884ffa..1cc189944141 100644
@@ -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)
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#20 lore raw

PATCH v2 12/23 arm64: smp: Don't bother printing the I-cache policy for each CPU

In reply to Will Deacon

The I-cache policy isn't particularly interesting but printing it early can result in unnecessary serialisation of onlining CPUs.

Remove the pointless print.

---
 arch/arm64/kernel/cpuinfo.c | 16 ----------------
 1 file changed, 16 deletions(-)
arch/arm64/kernel/cpuinfo.c +0 -16
index dfb21bf5541d..94e6cd211d70 100644
@@ -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)
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#21 lore raw

PATCH v2 13/23 arm64: smp: Defer RCU registration during secondary CPU bringup

In reply to Will Deacon

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.

---
 arch/arm64/kernel/smp.c | 6 +++---
 include/linux/rcutree.h | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)
arch/arm64/kernel/smp.c +3 -3
index e1b15cab9ed6..014b163ce658 100644
@@ -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);
include/linux/rcutree.h +1 -1
index 16a04202888b..e6ad4f2a475c 100644
@@ -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 */
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#22 lore raw

PATCH v2 14/23 arm64: smp: Use generic HOTPLUG_CORE_SYNC_FULL machinery for CPU onlining

In reply to Will Deacon

Select HOTPLUG_CORE_SYNC_FULL on arm64 to replace the 'cpu_running' completion with the generic code for synchronising with secondary CPUs during boot.

---
 arch/arm64/Kconfig           |  2 +-
 arch/arm64/include/asm/smp.h |  1 -
 arch/arm64/kernel/smp.c      | 39 +++++++++++++++++-------------------
 3 files changed, 19 insertions(+), 23 deletions(-)
arch/arm64/Kconfig +1 -1
index b5a51b0ef944..89d1f0f2269c 100644
@@ -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
arch/arm64/include/asm/smp.h +0 -1
index 10ea4f543069..fe343c30d620 100644
@@ -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 */
arch/arm64/kernel/smp.c +18 -21
index 014b163ce658..d83ba5bbfd37 100644
@@ -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.
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#23 lore raw

PATCH v2 15/23 arm64: smp: Use generic HOTPLUG_SPLIT_STARTUP machinery for CPU onlining

In reply to Will Deacon

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.

---
 arch/arm64/Kconfig           |  2 +-
 arch/arm64/include/asm/smp.h |  1 +
 arch/arm64/kernel/smp.c      | 10 ++++++----
 3 files changed, 8 insertions(+), 5 deletions(-)
arch/arm64/Kconfig +1 -1
index 89d1f0f2269c..fd8cf792b7fd 100644
@@ -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
arch/arm64/include/asm/smp.h +1 -0
index fe343c30d620..7b986a6a765b 100644
@@ -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;
arch/arm64/kernel/smp.c +6 -4
index d83ba5bbfd37..918b54d1c301 100644
@@ -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);
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#24 lore raw

PATCH v2 16/23 arm64: cpu_ops: Make 'cpu_operations' pointer global instead of per-cpu

In reply to Will Deacon

'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.

---
 arch/arm64/kernel/cpu_ops.c | 29 ++++++++++++++++++++---------
 1 file changed, 20 insertions(+), 9 deletions(-)
arch/arm64/kernel/cpu_ops.c +20 -9
index e133011f64b5..eacfb88a0c0c 100644
@@ -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;
 }
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#25 lore raw

PATCH v2 17/23 arm64: cpu_ops: Introduce get_secondary_cpu_ops()

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/include/asm/cpu_ops.h +1 -0
index a444c8915e88..cd298a8710d8 100644
@@ -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)
 {
arch/arm64/kernel/cpu_ops.c +5 -0
index eacfb88a0c0c..7d183ca31dc8 100644
@@ -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;
+}
arch/arm64/kernel/smp.c +7 -12
index 918b54d1c301..f7141ec18029 100644
@@ -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;
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#26 lore raw

PATCH v2 18/23 firmware/psci: Cache PSCI v0.2+ version number to avoid redundant SMCs

In reply to Will Deacon

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.

---
 drivers/firmware/psci/psci.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
drivers/firmware/psci/psci.c +6 -1
index e73bae6cb23a..8bb3f7c37678 100644
@@ -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)
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#27 lore raw

PATCH v2 19/23 firmware/psci: Extend ->cpu_on() callback to take an additional argument

In reply to Will Deacon

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.

---
 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(-)
arch/arm/kernel/psci_smp.c +2 -2
index 3bb0c4dcfc5c..bf05dbf433b1 100644
@@ -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;
 }
arch/arm64/kernel/psci.c +1 -1
index fabd732d0a2d..6b25a12ed143 100644
@@ -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);
 
drivers/firmware/psci/psci.c +14 -6
index 8bb3f7c37678..95034485cb4d 100644
@@ -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)
include/linux/psci.h +2 -1
index 4ca0060a3fc4..0f43868a0bee 100644
@@ -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);
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#28 lore raw

PATCH v2 20/23 arm64: cpu_ops: Expose optional argument to target cpu in ->cpu_boot()

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/include/asm/cpu_ops.h +5 -1
index cd298a8710d8..e7662a1879d9 100644
@@ -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);
arch/arm64/kernel/acpi_parking_protocol.c +2 -1
index e1be29e608b7..24ebde1241bf 100644
@@ -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;
arch/arm64/kernel/psci.c +10 -2
index 6b25a12ed143..8cd74a77de2e 100644
@@ -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,
arch/arm64/kernel/smp.c +1 -1
index f7141ec18029..29c70616a107 100644
@@ -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;
 }
arch/arm64/kernel/smp_spin_table.c +1 -1
index 49029eace3ad..a5e6f444c25f 100644
@@ -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.
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#29 lore raw

PATCH v2 21/23 arm64: smp: Pass secondary CPU boot parameters via firmware if possible

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/include/asm/smp.h +1 -0
index 7b986a6a765b..7f2cd84b7785 100644
@@ -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);
arch/arm64/kernel/head.S +11 -1
index 87a822e5c4ca..17868b497d7c 100644
@@ -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
arch/arm64/kernel/psci.c +2 -2
index 8cd74a77de2e..54aa91039b69 100644
@@ -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);
arch/arm64/kernel/smp.c +11 -14
index 29c70616a107..fb3a93090a3c 100644
@@ -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;
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#30 lore raw

PATCH v2 22/23 arm64: smp: Use generic HOTPLUG_PARALLEL machinery for CPU onlining

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/Kconfig +1 -1
index fd8cf792b7fd..8d963cec7189 100644
@@ -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
arch/arm64/include/asm/smp.h +20 -15
index 7f2cd84b7785..3decb42164aa 100644
@@ -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);
 }
arch/arm64/include/asm/topology.h +2 -0
index b9eaf4ad7085..f1ff9e40b7cd 100644
@@ -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 */
arch/arm64/kernel/head.S +5 -8
index 17868b497d7c..031ff1d4fd29 100644
@@ -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
arch/arm64/kernel/smp.c +42 -40

126 lines not on this page · raw message

arch/arm64/mm/mmu.c +1 -1
index 79d90226fd5d..59e572a09304 100644
@@ -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);
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
author maintainer
#31 lore raw

PATCH v2 23/23 arm64: smp: Harden parallel CPU bringup against broken PSCI firmware

In reply to Will Deacon

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.

---
 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(-)
arch/arm64/include/asm/smp.h +3 -1
index 3decb42164aa..1ecfa4fa4f82 100644
@@ -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__
 
arch/arm64/kernel/asm-offsets.c +1 -0
index 9c853ed3ceab..0cbd10af13ac 100644
@@ -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));
arch/arm64/kernel/head.S +33 -6
index 031ff1d4fd29..3f005e226547 100644
@@ -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.
  *
arch/arm64/kernel/smp.c +10 -2
index b3023557d789..8dc29bdef1a1 100644
@@ -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);
 
Signature · 1 line
2.56.0.385.gd3acb90ef8-goog
Tested-by on the v2 series
#32 lore raw
In reply to Will Deacon

From: Will Deacon <[email protected]> Sent: Friday, October 9, 2026 3:07 AM

Quoted 29 lines

Hi everyone,

This is version two of the patches I previously posted here:

https://lore.kernel.org/all/[email protected]/

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.

Quoted 7 lines

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.

As I reported [1] for Jinjie Ruan's original series, I tested this series on an ARM64 VM running on Hyper-V in the Azure cloud. Tested with 16 vCPUs in the VM and with 96 vCPUs in the VM. No issues. I mainly wanted to make sure nothing got crosswise with Hyper-V's implementation of PSCI.

[1] https://lore.kernel.org/linux-arm-kernel/SN6PR02MB41575CD5883C01FCB7F35E2DD4182@SN6PR02MB4157.namprd02.prod.outlook.com/

Quoted 82 lines

Cheers,

Will

Cc Thomas Gleixner <[email protected]>
Cc Ben Horgan <[email protected]>
Cc Ștefania Ion <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Pankaj Patil <[email protected]>
Cc Borislav Petkov <[email protected]>
Cc Lorenzo Pieralisi <[email protected]>
Cc Jinjie Ruan <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Tarun Sahu <[email protected]>
Cc Fuad Tabba <[email protected]>
Cc David Woodhouse <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Marc Zyngier <[email protected]>

--->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

Tested-by on the v2 series
#33 lore raw
In reply to Will Deacon
Quoted 28 lines · Will Deacon

Hi everyone,

This is version two of the patches I previously posted here:

https://lore.kernel.org/all/[email protected]/

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.

Quoted 6 lines

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.

Hi Will,

I tested this series on v7.3 rc6.

On Qemu (ms):

|     Platform     | baseline|   P=0   |   P=1  | Delta(%)|
| ---------------- | ------- | ------- | ------ | ------- |
| 128 vCPUs KVM    | 77.45   | 81.59   | 487.5  | -529.5% |
| 64-core ATF QEMU | 878.63  | 884.69  | 657.43 | 25.18%  |

The parallel boot time under KVM has become much slower, and it is unclear whether there is a concurrency bottleneck inside KVM.

And the parallel boot time of ATF-based QEMU is reduced by about 25%.

On Kunpeng 192-core HIP12 server (ms):

| v7.3 rc6  |   P=0    |   P=1    | Delta(%)|
| -------   | -------  | -------  | ------  |
| 14573.17  | 14573.17 | 8581.10  | 41.21%  |

But, similar to the test results of the previous version, the secondary cores boot in parallel, reducing the boot time by 41%.

Quoted 80 lines

Cheers,

Will

Cc Thomas Gleixner <[email protected]>
Cc Ben Horgan <[email protected]>
Cc Ștefania Ion <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Pankaj Patil <[email protected]>
Cc Borislav Petkov <[email protected]>
Cc Lorenzo Pieralisi <[email protected]>
Cc Jinjie Ruan <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Tarun Sahu <[email protected]>
Cc Fuad Tabba <[email protected]>
Cc David Woodhouse <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Marc Zyngier <[email protected]>

--->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(-)
Signature · 2 lines
Best regards,
Jinjie
maintainer
#34 lore raw

Re: (subset) [PATCH v2 00/23] arm64: Implement parallel CPU onlining with PSCI v0.2+

In reply to Will Deacon
Quoted 8 lines · Will Deacon

This is version two of the patches I previously posted here:

https://lore.kernel.org/all/[email protected]/

There have been quite a few changes since last time, thanks to all the feedback from humans and robots alike. These include:

[...]

Applied to arm64 (for-next/secondary-boot-prep), thanks!

[05/23] arm64: cpufeature: Check arm64_ftr_regs[] before the first store

        https://git.kernel.org/arm64/c/72add8bf4d57
[06/23] arm64: cpufeature: Add read_cpuid_with_overrides()
        https://git.kernel.org/arm64/c/cd9df8b01977
[07/23] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu()
        https://git.kernel.org/arm64/c/342e62ded77c
[08/23] arm64: cpufeature: Store every ID register with its overrides applied
        https://git.kernel.org/arm64/c/fbb6e5708311
[09/23] arm64: smp: Tidy up smp_prepare_cpus()
        https://git.kernel.org/arm64/c/9ddba588450f
[10/23] arm64: smp: Tidy up cpuinfo init and cpufeature updates
        https://git.kernel.org/arm64/c/f0a14972b5b5
[11/23] arm64: smp: Defer update of secondary CPU capabilities
        https://git.kernel.org/arm64/c/c8491bbf35e5
[12/23] arm64: smp: Don't bother printing the I-cache policy for each CPU
        https://git.kernel.org/arm64/c/6a52689f698a
[13/23] arm64: smp: Defer RCU registration during secondary CPU bringup
        https://git.kernel.org/arm64/c/e21f4de88aca