Skip to contentSkip to conversation

Add support for 32-bit tasks on asymmetric AArch32 systems

PATCH Superseded Will Deacon 5 participants 48 messages Jun 2, 2021 – Jun 10, 2021

Subsystems (9)

v8 of 11 Superseded Latest is v11 →

lore
Share & tools
lore

Will Deacon

author maintainer
#0 lore raw

v8 cover Add support for 32-bit tasks on asymmetric AArch32 systems

Hi again folks,

Here is v8 of the asymmetric 32-bit support patches that I previously posted here:

v1: https://lore.kernel.org/r/[email protected]
v2: https://lore.kernel.org/r/[email protected]
v3: https://lore.kernel.org/r/[email protected]
v4: https://lore.kernel.org/r/[email protected]
v5: https://lore.kernel.org/r/[email protected]
v6: https://lore.kernel.org/r/[email protected]
v7: https://lore.kernel.org/r/[email protected]

There was also a nice LWN writeup in case you've forgotten what this is about:

	https://lwn.net/Articles/838339/

Changes since v7 include:

* Dropped the scheduler migration fix, as Valentin has kindly fixed this separately in -tip:

https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/commit/?h=sched/core&id=475ea6c60279e9f2ddf7e4cf2648cd8ae0608361

* Dropped the freezer/ttwu changes, as Peter is rewriting that:

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

* Reworded documentation wrt KVM behaviour [maz]

* Tidied up control flow in cpuset_cpus_allowed_fallback() [peterz]

* Fixed interaction with migrate_disable() [peterz]

* Don't ignore allocation failure in restrict_cpus_allowed_ptr() [peterz]

* Reordered the patches to put arm64 prep work first

* Added some more acks/reviewed-by tags

Cheers,

Will

Cc Catalin Marinas <[email protected]>
Cc Marc Zyngier <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Morten Rasmussen <[email protected]>
Cc Qais Yousef <[email protected]>
Cc Suren Baghdasaryan <[email protected]>
Cc Quentin Perret <[email protected]>
Cc Tejun Heo <[email protected]>
Cc Johannes Weiner <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Juri Lelli <[email protected]>
Cc Vincent Guittot <[email protected]>
Cc "Rafael J. Wysocki" <[email protected]>
Cc Dietmar Eggemann <[email protected]>
Cc Daniel Bristot de Oliveira <[email protected]>
Cc Valentin Schneider <[email protected]>

--->8

Will Deacon (19):
arm64: cpuinfo: Split AArch32 registers out into a separate struct
arm64: Allow mismatched 32-bit EL0 support
KVM: arm64: Kill 32-bit vCPUs on systems with mismatched EL0 support
arm64: Kill 32-bit applications scheduled on 64-bit-only CPUs
sched: Introduce task_cpu_possible_mask() to limit fallback rq
selection
cpuset: Don't use the cpu_possible_mask as a last resort for cgroup v1
cpuset: Honour task_cpu_possible_mask() in guarantee_online_cpus()
sched: Reject CPU affinity changes based on task_cpu_possible_mask()
sched: Introduce task_struct::user_cpus_ptr to track requested
affinity
sched: Split the guts of sched_setaffinity() into a helper function
sched: Allow task CPU affinity to be restricted on asymmetric systems
sched: Introduce task_cpus_dl_admissible() to check proposed affinity
arm64: Implement task_cpu_possible_mask()
arm64: exec: Adjust affinity for compat tasks with mismatched 32-bit

    EL0

arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched
system
arm64: Advertise CPUs capable of running 32-bit applications in sysfs
arm64: Hook up cmdline parameter to allow mismatched 32-bit EL0
arm64: Remove logic to kill 32-bit tasks on 64-bit-only cores
Documentation: arm64: describe asymmetric 32-bit support

 .../ABI/testing/sysfs-devices-system-cpu      |   9 +
 .../admin-guide/kernel-parameters.txt         |  11 +
 Documentation/arm64/asymmetric-32bit.rst      | 155 ++++++++
 Documentation/arm64/index.rst                 |   1 +
 arch/arm64/include/asm/cpu.h                  |  44 +--
 arch/arm64/include/asm/cpufeature.h           |   8 +-
 arch/arm64/include/asm/elf.h                  |   6 +-
 arch/arm64/include/asm/mmu_context.h          |  13 +
 arch/arm64/kernel/cpufeature.c                | 227 +++++++++---
 arch/arm64/kernel/cpuinfo.c                   |  53 +--
 arch/arm64/kernel/process.c                   |  44 ++-
 arch/arm64/kvm/arm.c                          |  11 +-
 arch/arm64/tools/cpucaps                      |   3 +-
 include/linux/cpuset.h                        |   3 +-
 include/linux/mmu_context.h                   |  11 +
 include/linux/sched.h                         |  21 ++
 init/init_task.c                              |   1 +
 kernel/cgroup/cpuset.c                        |  49 ++-
 kernel/fork.c                                 |   2 +
 kernel/sched/core.c                           | 334 ++++++++++++++----
 kernel/sched/sched.h                          |   1 +
 21 files changed, 819 insertions(+), 188 deletions(-)
 create mode 100644 Documentation/arm64/asymmetric-32bit.rst
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#1 lore raw

PATCH v8 1/19 arm64: cpuinfo: Split AArch32 registers out into a separate struct

In preparation for late initialisation of the "sanitised" AArch32 register state, move the AArch32 registers out of 'struct cpuinfo' and into their own struct definition.

---
 arch/arm64/include/asm/cpu.h   | 44 +++++++++++----------
 arch/arm64/kernel/cpufeature.c | 71 ++++++++++++++++++----------------
 arch/arm64/kernel/cpuinfo.c    | 53 +++++++++++++------------
 3 files changed, 89 insertions(+), 79 deletions(-)
arch/arm64/include/asm/cpu.h +24 -20
index 7faae6ff3ab4..f4e01aa0f442 100644
@@ -12,26 +12,7 @@
 /*
  * Records attributes of an individual CPU.
  */
-struct cpuinfo_arm64 {
-	struct cpu	cpu;
-	struct kobject	kobj;
-	u32		reg_ctr;
-	u32		reg_cntfrq;
-	u32		reg_dczid;
-	u32		reg_midr;
-	u32		reg_revidr;
-
-	u64		reg_id_aa64dfr0;
-	u64		reg_id_aa64dfr1;
-	u64		reg_id_aa64isar0;
-	u64		reg_id_aa64isar1;
-	u64		reg_id_aa64mmfr0;
-	u64		reg_id_aa64mmfr1;
-	u64		reg_id_aa64mmfr2;
-	u64		reg_id_aa64pfr0;
-	u64		reg_id_aa64pfr1;
-	u64		reg_id_aa64zfr0;
-
+struct cpuinfo_32bit {
 	u32		reg_id_dfr0;
 	u32		reg_id_dfr1;
 	u32		reg_id_isar0;
@@ -54,6 +35,29 @@ struct cpuinfo_arm64 {
 	u32		reg_mvfr0;
 	u32		reg_mvfr1;
 	u32		reg_mvfr2;
+};
+
+struct cpuinfo_arm64 {
+	struct cpu	cpu;
+	struct kobject	kobj;
+	u32		reg_ctr;
+	u32		reg_cntfrq;
+	u32		reg_dczid;
+	u32		reg_midr;
+	u32		reg_revidr;
+
+	u64		reg_id_aa64dfr0;
+	u64		reg_id_aa64dfr1;
+	u64		reg_id_aa64isar0;
+	u64		reg_id_aa64isar1;
+	u64		reg_id_aa64mmfr0;
+	u64		reg_id_aa64mmfr1;
+	u64		reg_id_aa64mmfr2;
+	u64		reg_id_aa64pfr0;
+	u64		reg_id_aa64pfr1;
+	u64		reg_id_aa64zfr0;
+
+	struct cpuinfo_32bit	aarch32;
 
 	/* pseudo-ZCR for recording maximum ZCR_EL1 LEN value: */
 	u64		reg_zcr;
arch/arm64/kernel/cpufeature.c +37 -34

102 lines not on this page · raw message

arch/arm64/kernel/cpuinfo.c +28 -25

65 lines not on this page · raw message

Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
reviewer Acked-by on v8 1/19
#2 lore raw

Re: [PATCH v8 01/19] arm64: cpuinfo: Split AArch32 registers out into a separate struct

Quoted 6 lines · Will Deacon

In preparation for late initialisation of the "sanitised" AArch32 register state, move the AArch32 registers out of 'struct cpuinfo' and into their own struct definition.

Makes sense to me; if it's not too painful to change, I'd suggest aarch32 rather than 32bit in the name, but either way:

Mark.

Quoted 252 lines
---
 arch/arm64/include/asm/cpu.h   | 44 +++++++++++----------
 arch/arm64/kernel/cpufeature.c | 71 ++++++++++++++++++----------------
 arch/arm64/kernel/cpuinfo.c    | 53 +++++++++++++------------
 3 files changed, 89 insertions(+), 79 deletions(-)
arch/arm64/include/asm/cpu.h +24 -20
index 7faae6ff3ab4..f4e01aa0f442 100644
@@ -12,26 +12,7 @@
 /*
  * Records attributes of an individual CPU.
  */
-struct cpuinfo_arm64 {
-	struct cpu	cpu;
-	struct kobject	kobj;
-	u32		reg_ctr;
-	u32		reg_cntfrq;
-	u32		reg_dczid;
-	u32		reg_midr;
-	u32		reg_revidr;
-
-	u64		reg_id_aa64dfr0;
-	u64		reg_id_aa64dfr1;
-	u64		reg_id_aa64isar0;
-	u64		reg_id_aa64isar1;
-	u64		reg_id_aa64mmfr0;
-	u64		reg_id_aa64mmfr1;
-	u64		reg_id_aa64mmfr2;
-	u64		reg_id_aa64pfr0;
-	u64		reg_id_aa64pfr1;
-	u64		reg_id_aa64zfr0;
-
+struct cpuinfo_32bit {
 	u32		reg_id_dfr0;
 	u32		reg_id_dfr1;
 	u32		reg_id_isar0;
@@ -54,6 +35,29 @@ struct cpuinfo_arm64 {
 	u32		reg_mvfr0;
 	u32		reg_mvfr1;
 	u32		reg_mvfr2;
+};
+
+struct cpuinfo_arm64 {
+	struct cpu	cpu;
+	struct kobject	kobj;
+	u32		reg_ctr;
+	u32		reg_cntfrq;
+	u32		reg_dczid;
+	u32		reg_midr;
+	u32		reg_revidr;
+
+	u64		reg_id_aa64dfr0;
+	u64		reg_id_aa64dfr1;
+	u64		reg_id_aa64isar0;
+	u64		reg_id_aa64isar1;
+	u64		reg_id_aa64mmfr0;
+	u64		reg_id_aa64mmfr1;
+	u64		reg_id_aa64mmfr2;
+	u64		reg_id_aa64pfr0;
+	u64		reg_id_aa64pfr1;
+	u64		reg_id_aa64zfr0;
+
+	struct cpuinfo_32bit	aarch32;
 
 	/* pseudo-ZCR for recording maximum ZCR_EL1 LEN value: */
 	u64		reg_zcr;
arch/arm64/kernel/cpufeature.c +37 -34
index efed2830d141..a4db25cd7122 100644
@@ -863,6 +863,31 @@ static void __init init_cpu_hwcaps_indirect_list(void)
 
 static void __init setup_boot_cpu_capabilities(void);
 
+static void __init init_32bit_cpu_features(struct cpuinfo_32bit *info)
+{
+	init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
+	init_cpu_ftr_reg(SYS_ID_DFR1_EL1, info->reg_id_dfr1);
+	init_cpu_ftr_reg(SYS_ID_ISAR0_EL1, info->reg_id_isar0);
+	init_cpu_ftr_reg(SYS_ID_ISAR1_EL1, info->reg_id_isar1);
+	init_cpu_ftr_reg(SYS_ID_ISAR2_EL1, info->reg_id_isar2);
+	init_cpu_ftr_reg(SYS_ID_ISAR3_EL1, info->reg_id_isar3);
+	init_cpu_ftr_reg(SYS_ID_ISAR4_EL1, info->reg_id_isar4);
+	init_cpu_ftr_reg(SYS_ID_ISAR5_EL1, info->reg_id_isar5);
+	init_cpu_ftr_reg(SYS_ID_ISAR6_EL1, info->reg_id_isar6);
+	init_cpu_ftr_reg(SYS_ID_MMFR0_EL1, info->reg_id_mmfr0);
+	init_cpu_ftr_reg(SYS_ID_MMFR1_EL1, info->reg_id_mmfr1);
+	init_cpu_ftr_reg(SYS_ID_MMFR2_EL1, info->reg_id_mmfr2);
+	init_cpu_ftr_reg(SYS_ID_MMFR3_EL1, info->reg_id_mmfr3);
+	init_cpu_ftr_reg(SYS_ID_MMFR4_EL1, info->reg_id_mmfr4);
+	init_cpu_ftr_reg(SYS_ID_MMFR5_EL1, info->reg_id_mmfr5);
+	init_cpu_ftr_reg(SYS_ID_PFR0_EL1, info->reg_id_pfr0);
+	init_cpu_ftr_reg(SYS_ID_PFR1_EL1, info->reg_id_pfr1);
+	init_cpu_ftr_reg(SYS_ID_PFR2_EL1, info->reg_id_pfr2);
+	init_cpu_ftr_reg(SYS_MVFR0_EL1, info->reg_mvfr0);
+	init_cpu_ftr_reg(SYS_MVFR1_EL1, info->reg_mvfr1);
+	init_cpu_ftr_reg(SYS_MVFR2_EL1, info->reg_mvfr2);
+}
+
 void __init init_cpu_features(struct cpuinfo_arm64 *info)
 {
 	/* Before we start using the tables, make sure it is sorted */
@@ -882,29 +907,8 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info)
 	init_cpu_ftr_reg(SYS_ID_AA64PFR1_EL1, info->reg_id_aa64pfr1);
 	init_cpu_ftr_reg(SYS_ID_AA64ZFR0_EL1, info->reg_id_aa64zfr0);
 
-	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
-		init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
-		init_cpu_ftr_reg(SYS_ID_DFR1_EL1, info->reg_id_dfr1);
-		init_cpu_ftr_reg(SYS_ID_ISAR0_EL1, info->reg_id_isar0);
-		init_cpu_ftr_reg(SYS_ID_ISAR1_EL1, info->reg_id_isar1);
-		init_cpu_ftr_reg(SYS_ID_ISAR2_EL1, info->reg_id_isar2);
-		init_cpu_ftr_reg(SYS_ID_ISAR3_EL1, info->reg_id_isar3);
-		init_cpu_ftr_reg(SYS_ID_ISAR4_EL1, info->reg_id_isar4);
-		init_cpu_ftr_reg(SYS_ID_ISAR5_EL1, info->reg_id_isar5);
-		init_cpu_ftr_reg(SYS_ID_ISAR6_EL1, info->reg_id_isar6);
-		init_cpu_ftr_reg(SYS_ID_MMFR0_EL1, info->reg_id_mmfr0);
-		init_cpu_ftr_reg(SYS_ID_MMFR1_EL1, info->reg_id_mmfr1);
-		init_cpu_ftr_reg(SYS_ID_MMFR2_EL1, info->reg_id_mmfr2);
-		init_cpu_ftr_reg(SYS_ID_MMFR3_EL1, info->reg_id_mmfr3);
-		init_cpu_ftr_reg(SYS_ID_MMFR4_EL1, info->reg_id_mmfr4);
-		init_cpu_ftr_reg(SYS_ID_MMFR5_EL1, info->reg_id_mmfr5);
-		init_cpu_ftr_reg(SYS_ID_PFR0_EL1, info->reg_id_pfr0);
-		init_cpu_ftr_reg(SYS_ID_PFR1_EL1, info->reg_id_pfr1);
-		init_cpu_ftr_reg(SYS_ID_PFR2_EL1, info->reg_id_pfr2);
-		init_cpu_ftr_reg(SYS_MVFR0_EL1, info->reg_mvfr0);
-		init_cpu_ftr_reg(SYS_MVFR1_EL1, info->reg_mvfr1);
-		init_cpu_ftr_reg(SYS_MVFR2_EL1, info->reg_mvfr2);
-	}
+	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
+		init_32bit_cpu_features(&info->aarch32);
 
 	if (id_aa64pfr0_sve(info->reg_id_aa64pfr0)) {
 		init_cpu_ftr_reg(SYS_ZCR_EL1, info->reg_zcr);
@@ -975,20 +979,12 @@ static void relax_cpu_ftr_reg(u32 sys_id, int field)
 	WARN_ON(!ftrp->width);
 }
 
-static int update_32bit_cpu_features(int cpu, struct cpuinfo_arm64 *info,
-				     struct cpuinfo_arm64 *boot)
+static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info,
+				     struct cpuinfo_32bit *boot)
 {
 	int taint = 0;
 	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
 
-	/*
-	 * 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.
-	 */
-	if (!id_aa64pfr0_32bit_el0(pfr0))
-		return taint;
-
 	/*
 	 * If we don't have AArch32 at EL1, then relax the strictness of
 	 * EL1-dependent register fields to avoid spurious sanity check fails.
@@ -1135,10 +1131,17 @@ void update_cpu_features(int cpu,
 	}
 
 	/*
+	 * 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.
 	 */
-	taint |= update_32bit_cpu_features(cpu, info, boot);
+	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
+		taint |= update_32bit_cpu_features(cpu, &info->aarch32,
+						   &boot->aarch32);
+	}
 
 	/*
 	 * Mismatched CPU features are a recipe for disaster. Don't even
arch/arm64/kernel/cpuinfo.c +28 -25
index 51fcf99d5351..264c119a6cae 100644
@@ -344,6 +344,32 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info)
 	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)
+{
+	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_mvfr0 = read_cpuid(MVFR0_EL1);
+	info->reg_mvfr1 = read_cpuid(MVFR1_EL1);
+	info->reg_mvfr2 = read_cpuid(MVFR2_EL1);
+}
+
 static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
 {
 	info->reg_cntfrq = arch_timer_get_cntfrq();
@@ -371,31 +397,8 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info)
 	info->reg_id_aa64pfr1 = read_cpuid(ID_AA64PFR1_EL1);
 	info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1);
 
-	/* Update the 32bit ID registers only if AArch32 is implemented */
-	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
-		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_mvfr0 = read_cpuid(MVFR0_EL1);
-		info->reg_mvfr1 = read_cpuid(MVFR1_EL1);
-		info->reg_mvfr2 = read_cpuid(MVFR2_EL1);
-	}
+	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
+		__cpuinfo_store_cpu_32bit(&info->aarch32);
 
 	if (IS_ENABLED(CONFIG_ARM64_SVE) &&
 	    id_aa64pfr0_sve(info->reg_id_aa64pfr0))
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#3 lore raw

Re: [PATCH v8 01/19] arm64: cpuinfo: Split AArch32 registers out into a separate struct

Quoted 5 lines · Mark Rutland
Quoted 4 lines · Will Deacon

In preparation for late initialisation of the "sanitised" AArch32 register state, move the AArch32 registers out of 'struct cpuinfo' and into their own struct definition.

Quoted 7 lines
Quoted 2 lines

Makes sense to me; if it's not too painful to change, I'd suggest aarch32 rather than 32bit in the name, but either way:

Thanks. "32bit" is already pervasive in cpufeature.c and we're using arm64 instead of aarch64 in cpuinfo_arm64, so I'll leave this as-is and offer somebody else the refactoring opportunity ;)

Will

author maintainer
#4 lore raw

PATCH v8 2/19 arm64: Allow mismatched 32-bit EL0 support

In reply to Will Deacon

When confronted with a mixture of CPUs, some of which support 32-bit applications and others which don't, we quite sensibly treat the system as 64-bit only for userspace and prevent execve() of 32-bit binaries.

Unfortunately, some crazy folks have decided to build systems like this with the intention of running 32-bit applications, so relax our sanitisation logic to continue to advertise 32-bit support to userspace on these systems and track the real 32-bit capable cores in a cpumask instead. For now, the default behaviour remains but will be tied to a command-line option in a later patch.

---
 arch/arm64/include/asm/cpufeature.h |   8 +-
 arch/arm64/kernel/cpufeature.c      | 114 ++++++++++++++++++++++++----
 arch/arm64/tools/cpucaps            |   3 +-
 3 files changed, 110 insertions(+), 15 deletions(-)
arch/arm64/include/asm/cpufeature.h +7 -1
index 338840c00e8e..603bf4160cd6 100644
@@ -630,9 +630,15 @@ static inline bool cpu_supports_mixed_endian_el0(void)
 	return id_aa64mmfr0_mixed_endian_el0(read_cpuid(ID_AA64MMFR0_EL1));
 }
 
+const struct cpumask *system_32bit_el0_cpumask(void);
+DECLARE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
 static inline bool system_supports_32bit_el0(void)
 {
-	return cpus_have_const_cap(ARM64_HAS_32BIT_EL0);
+	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	return static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       id_aa64pfr0_32bit_el0(pfr0);
 }
 
 static inline bool system_supports_4kb_granule(void)
arch/arm64/kernel/cpufeature.c +101 -13

193 lines not on this page · raw message

arch/arm64/tools/cpucaps +2 -1
index 21fbdda7086e..49305c2e6dfd 100644
@@ -3,7 +3,8 @@
 # Internal CPU capabilities constants, keep this list sorted
 
 BTI
-HAS_32BIT_EL0
+# Unreliable: use system_supports_32bit_el0() instead.
+HAS_32BIT_EL0_DO_NOT_USE
 HAS_32BIT_EL1
 HAS_ADDRESS_AUTH
 HAS_ADDRESS_AUTH_ARCH
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
reviewer
#5 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 28 lines · Will Deacon

When confronted with a mixture of CPUs, some of which support 32-bit applications and others which don't, we quite sensibly treat the system as 64-bit only for userspace and prevent execve() of 32-bit binaries.

Unfortunately, some crazy folks have decided to build systems like this with the intention of running 32-bit applications, so relax our sanitisation logic to continue to advertise 32-bit support to userspace on these systems and track the real 32-bit capable cores in a cpumask instead. For now, the default behaviour remains but will be tied to a command-line option in a later patch.

---
 arch/arm64/include/asm/cpufeature.h |   8 +-
 arch/arm64/kernel/cpufeature.c      | 114 ++++++++++++++++++++++++----
 arch/arm64/tools/cpucaps            |   3 +-
 3 files changed, 110 insertions(+), 15 deletions(-)
arch/arm64/include/asm/cpufeature.h +1 -0
index 338840c00e8e..603bf4160cd6 100644
@@ -630,9 +630,15 @@ static inline bool cpu_supports_mixed_endian_el0(void)
 	return id_aa64mmfr0_mixed_endian_el0(read_cpuid(ID_AA64MMFR0_EL1));
 }
 
+const struct cpumask *system_32bit_el0_cpumask(void);

arch/arm64/include/asm/cpufeature.h static inline bool cpu_supports_mixed_endian_el0(void) · line 642

Quoted 10 lines
arch/arm64/include/asm/cpufeature.h +6 -1
+DECLARE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
 static inline bool system_supports_32bit_el0(void)
 {
-	return cpus_have_const_cap(ARM64_HAS_32BIT_EL0);
+	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	return static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       id_aa64pfr0_32bit_el0(pfr0);
 }

Note that read_sanitised_ftr_reg() has to do a bsearch() to find the arm64_ftr_reg, so this will make system_32bit_el0_cpumask() a fair amount more expensive than it needs to be.

Can we follow the pattern we have for arm64_ftr_reg_ctrel0, and have a arm64_ftr_reg_id_aa64pfr0_el1 that we can address directly here?

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))
}
Quoted 49 lines
arch/arm64/include/asm/cpufeature.h +0 -0
 static inline bool system_supports_4kb_granule(void)
arch/arm64/kernel/cpufeature.c +20 -2
index a4db25cd7122..4194a47de62d 100644
@@ -107,6 +107,24 @@ DECLARE_BITMAP(boot_capabilities, ARM64_NPATCHABLE);
 bool arm64_use_ng_mappings = false;
 EXPORT_SYMBOL(arm64_use_ng_mappings);
 
+/*
+ * Permit PER_LINUX32 and execve() of 32-bit binaries even if not all CPUs
+ * support it?
+ */
+static bool __read_mostly allow_mismatched_32bit_el0;
+
+/*
+ * Static branch enabled only if allow_mismatched_32bit_el0 is set and we have
+ * seen at least one CPU capable of 32-bit EL0.
+ */
+DEFINE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
+/*
+ * Mask of CPUs supporting 32-bit EL0.
+ * Only valid if arm64_mismatched_32bit_el0 is enabled.
+ */
+static cpumask_var_t cpu_32bit_el0_mask __cpumask_var_read_mostly;
+
 /*
  * Flag to indicate if we have computed the system wide
  * capabilities based on the boot time active CPUs. This
@@ -767,7 +785,7 @@ static void __init sort_ftr_regs(void)
  * Any bits that are not covered by an arm64_ftr_bits entry are considered
  * RES0 for the system-wide value, and must strictly match.
  */
-static void __init init_cpu_ftr_reg(u32 sys_reg, u64 new)
+static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
 {
 	u64 val = 0;
 	u64 strict_mask = ~0x0ULL;
@@ -863,7 +881,7 @@ static void __init init_cpu_hwcaps_indirect_list(void)
 
 static void __init setup_boot_cpu_capabilities(void);
 
-static void __init init_32bit_cpu_features(struct cpuinfo_32bit *info)
+static void init_32bit_cpu_features(struct cpuinfo_32bit *info)
 {
 	init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
 	init_cpu_ftr_reg(SYS_ID_DFR1_EL1, info->reg_id_dfr1);

arch/arm64/kernel/cpufeature.c static void relax_cpu_ftr_reg(u32 sys_id, int field) · line 1001

Quoted 5 lines
arch/arm64/kernel/cpufeature.c +2 -0
@@ -979,6 +997,22 @@ static void relax_cpu_ftr_reg(u32 sys_id, int field)
 	WARN_ON(!ftrp->width);
 }
 
+static void update_mismatched_32bit_el0_cpu_features(struct cpuinfo_arm64 *info,
+						     struct cpuinfo_arm64 *boot)

Could we s/update/lazy_init/ here?

IIUC this caters for the case where CPU0 doesn't have AArch32 but a secondary does. That, and the naming looks odd in update_cpu_features() when we have:

	update_mismatched_32bit_el0_cpu_features(...)
	update_32bit_cpu_features(...);

arch/arm64/kernel/cpufeature.c static void relax_cpu_ftr_reg(u32 sys_id, int field) · line 1014

Quoted 13 lines
arch/arm64/kernel/cpufeature.c +13 -0
+{
+	static bool boot_cpu_32bit_regs_overridden = false;
+
+	if (!allow_mismatched_32bit_el0 || boot_cpu_32bit_regs_overridden)
+		return;
+
+	if (id_aa64pfr0_32bit_el0(boot->reg_id_aa64pfr0))
+		return;
+
+	boot->aarch32 = info->aarch32;
+	init_32bit_cpu_features(&boot->aarch32);
+	boot_cpu_32bit_regs_overridden = true;
+}

Can't we share this with the boot CPU path if we do:

/*
* Initialize the common AArch32 features on the first CPU with AArch32. */
static void lazy_init_32bit_el0_cpu_features(struct cpuinfo_arm64 *info,
struct cpuinfo_arm64 *boot)

{
	static bool initialised = false;
	if (initialised || !id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
		return;
	
	boot->aarch32 = info->aarch32;
	init_32bit_cpu_features(&boot->aarch32);
	initiaised = true;
}

... or is the allow_mismatched_32bit_el0 check necessary for late hotplug?

Quoted 129 lines
arch/arm64/kernel/cpufeature.c +54 -11
+
 static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info,
 				     struct cpuinfo_32bit *boot)
 {
@@ -1139,6 +1173,7 @@ void update_cpu_features(int cpu,
 	 * (e.g. SYS_ID_AA64PFR0_EL1), so we call it last.
 	 */
 	if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
+		update_mismatched_32bit_el0_cpu_features(info, boot);
 		taint |= update_32bit_cpu_features(cpu, &info->aarch32,
 						   &boot->aarch32);
 	}
@@ -1251,6 +1286,28 @@ has_cpuid_feature(const struct arm64_cpu_capabilities *entry, int scope)
 	return feature_matches(val, entry);
 }
 
+const struct cpumask *system_32bit_el0_cpumask(void)
+{
+	if (!system_supports_32bit_el0())
+		return cpu_none_mask;
+
+	if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
+		return cpu_32bit_el0_mask;
+
+	return cpu_possible_mask;
+}
+
+static bool has_32bit_el0(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	if (!has_cpuid_feature(entry, scope))
+		return allow_mismatched_32bit_el0;
+
+	if (scope == SCOPE_SYSTEM)
+		pr_info("detected: 32-bit EL0 Support\n");
+
+	return true;
+}
+
 static bool has_useable_gicv3_cpuif(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	bool has_sre;
@@ -1869,10 +1926,9 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		.cpu_enable = cpu_copy_el2regs,
 	},
 	{
-		.desc = "32-bit EL0 Support",
-		.capability = ARM64_HAS_32BIT_EL0,
+		.capability = ARM64_HAS_32BIT_EL0_DO_NOT_USE,
 		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
-		.matches = has_cpuid_feature,
+		.matches = has_32bit_el0,
 		.sys_reg = SYS_ID_AA64PFR0_EL1,
 		.sign = FTR_UNSIGNED,
 		.field_pos = ID_AA64PFR0_EL0_SHIFT,
@@ -2381,7 +2437,7 @@ static const struct arm64_cpu_capabilities compat_elf_hwcaps[] = {
 	{},
 };
 
-static void __init cap_set_elf_hwcap(const struct arm64_cpu_capabilities *cap)
+static void cap_set_elf_hwcap(const struct arm64_cpu_capabilities *cap)
 {
 	switch (cap->hwcap_type) {
 	case CAP_HWCAP:
@@ -2426,7 +2482,7 @@ static bool cpus_have_elf_hwcap(const struct arm64_cpu_capabilities *cap)
 	return rc;
 }
 
-static void __init setup_elf_hwcaps(const struct arm64_cpu_capabilities *hwcaps)
+static void setup_elf_hwcaps(const struct arm64_cpu_capabilities *hwcaps)
 {
 	/* We support emulation of accesses to CPU ID feature registers */
 	cpu_set_named_feature(CPUID);
@@ -2601,7 +2657,7 @@ static void check_early_cpu_features(void)
 }
 
 static void
-verify_local_elf_hwcaps(const struct arm64_cpu_capabilities *caps)
+__verify_local_elf_hwcaps(const struct arm64_cpu_capabilities *caps)
 {
 
 	for (; caps->matches; caps++)
@@ -2612,6 +2668,14 @@ verify_local_elf_hwcaps(const struct arm64_cpu_capabilities *caps)
 		}
 }
 
+static void verify_local_elf_hwcaps(void)
+{
+	__verify_local_elf_hwcaps(arm64_elf_hwcaps);
+
+	if (id_aa64pfr0_32bit_el0(read_cpuid(ID_AA64PFR0_EL1)))
+		__verify_local_elf_hwcaps(compat_elf_hwcaps);
+}
+
 static void verify_sve_features(void)
 {
 	u64 safe_zcr = read_sanitised_ftr_reg(SYS_ZCR_EL1);
@@ -2676,11 +2740,7 @@ static void verify_local_cpu_capabilities(void)
 	 * on all secondary CPUs.
 	 */
 	verify_local_cpu_caps(SCOPE_ALL & ~SCOPE_BOOT_CPU);
-
-	verify_local_elf_hwcaps(arm64_elf_hwcaps);
-
-	if (system_supports_32bit_el0())
-		verify_local_elf_hwcaps(compat_elf_hwcaps);
+	verify_local_elf_hwcaps();
 
 	if (system_supports_sve())
 		verify_sve_features();
@@ -2815,6 +2875,34 @@ void __init setup_cpu_features(void)
 			ARCH_DMA_MINALIGN);
 }
 
+static int enable_mismatched_32bit_el0(unsigned int cpu)
+{
+	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
+	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
+
+	if (cpu_32bit) {
+		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
+		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
+		setup_elf_hwcaps(compat_elf_hwcaps);
+	}
+
+	return 0;
+}
+
+static int __init init_32bit_el0_mask(void)
+{

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2903

Quoted 10 lines
arch/arm64/kernel/cpufeature.c +10 -0
+	if (!allow_mismatched_32bit_el0)
+		return 0;
+
+	if (!zalloc_cpumask_var(&cpu_32bit_el0_mask, GFP_KERNEL))
+		return -ENOMEM;
+
+	return cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+				 "arm64/mismatched_32bit_el0:online",
+				 enable_mismatched_32bit_el0, NULL);
+}

Shouldn't we clear this on a hot-unplug?

Thanks,
Mark.

Quoted 22 lines
arch/arm64/kernel/cpufeature.c +2 -0
+subsys_initcall_sync(init_32bit_el0_mask);
+
 static void __maybe_unused cpu_enable_cnp(struct arm64_cpu_capabilities const *cap)
 {
 	cpu_replace_ttbr1(lm_alias(swapper_pg_dir));
arch/arm64/tools/cpucaps +2 -1
index 21fbdda7086e..49305c2e6dfd 100644
@@ -3,7 +3,8 @@
 # Internal CPU capabilities constants, keep this list sorted
 
 BTI
-HAS_32BIT_EL0
+# Unreliable: use system_supports_32bit_el0() instead.
+HAS_32BIT_EL0_DO_NOT_USE
 HAS_32BIT_EL1
 HAS_ADDRESS_AUTH
 HAS_ADDRESS_AUTH_ARCH
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#6 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 28 lines · Mark Rutland
Quoted 27 lines · Will Deacon

When confronted with a mixture of CPUs, some of which support 32-bit applications and others which don't, we quite sensibly treat the system as 64-bit only for userspace and prevent execve() of 32-bit binaries.

Unfortunately, some crazy folks have decided to build systems like this with the intention of running 32-bit applications, so relax our sanitisation logic to continue to advertise 32-bit support to userspace on these systems and track the real 32-bit capable cores in a cpumask instead. For now, the default behaviour remains but will be tied to a command-line option in a later patch.

---
 arch/arm64/include/asm/cpufeature.h |   8 +-
 arch/arm64/kernel/cpufeature.c      | 114 ++++++++++++++++++++++++----
 arch/arm64/tools/cpucaps            |   3 +-
 3 files changed, 110 insertions(+), 15 deletions(-)
arch/arm64/include/asm/cpufeature.h +0 -0
index 338840c00e8e..603bf4160cd6 100644
@@ -630,9 +630,15 @@ static inline bool cpu_supports_mixed_endian_el0(void)
 	return id_aa64mmfr0_mixed_endian_el0(read_cpuid(ID_AA64MMFR0_EL1));
 }
 
Quoted 15 lines

arch/arm64/include/asm/cpufeature.h static inline bool cpu_supports_mixed_endian_el0(void) · line 642

Quoted 11 lines
arch/arm64/include/asm/cpufeature.h +7 -1
+const struct cpumask *system_32bit_el0_cpumask(void);
+DECLARE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
 static inline bool system_supports_32bit_el0(void)
 {
-	return cpus_have_const_cap(ARM64_HAS_32BIT_EL0);
+	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	return static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       id_aa64pfr0_32bit_el0(pfr0);
 }

Note that read_sanitised_ftr_reg() has to do a bsearch() to find the arm64_ftr_reg, so this will make system_32bit_el0_cpumask() a fair amount more expensive than it needs to be.

I seriously doubt that it matters, but it did come up before and I proposed a potential solution if it's actually a concern:

https://lore.kernel.org/r/20201202172727.GC29813@willie-the-truck

so if you can show that it's a problem, we can resurrect something like that.

Quoted 2 lines

Can we follow the pattern we have for arm64_ftr_reg_ctrel0, and have a arm64_ftr_reg_id_aa64pfr0_el1 that we can address directly here?

I mean, clearly its possible, but based on what data?

Quoted 6 lines

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

Quoted 7 lines

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))

Something similar was discussed in November last year but this falls apart with late onlining because its not generally possible to tell whether you've seen all the CPUs or not.

I'm mostly reluctant to make significant changes based on cosmetic preferences because testing and debugging this with all the system combinations is really difficult. Do you see a functional issue with what I have?

Quoted 57 lines
Quoted 49 lines
arch/arm64/include/asm/cpufeature.h +0 -0
 static inline bool system_supports_4kb_granule(void)
arch/arm64/kernel/cpufeature.c +20 -2
index a4db25cd7122..4194a47de62d 100644
@@ -107,6 +107,24 @@ DECLARE_BITMAP(boot_capabilities, ARM64_NPATCHABLE);
 bool arm64_use_ng_mappings = false;
 EXPORT_SYMBOL(arm64_use_ng_mappings);
 
+/*
+ * Permit PER_LINUX32 and execve() of 32-bit binaries even if not all CPUs
+ * support it?
+ */
+static bool __read_mostly allow_mismatched_32bit_el0;
+
+/*
+ * Static branch enabled only if allow_mismatched_32bit_el0 is set and we have
+ * seen at least one CPU capable of 32-bit EL0.
+ */
+DEFINE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
+/*
+ * Mask of CPUs supporting 32-bit EL0.
+ * Only valid if arm64_mismatched_32bit_el0 is enabled.
+ */
+static cpumask_var_t cpu_32bit_el0_mask __cpumask_var_read_mostly;
+
 /*
  * Flag to indicate if we have computed the system wide
  * capabilities based on the boot time active CPUs. This
@@ -767,7 +785,7 @@ static void __init sort_ftr_regs(void)
  * Any bits that are not covered by an arm64_ftr_bits entry are considered
  * RES0 for the system-wide value, and must strictly match.
  */
-static void __init init_cpu_ftr_reg(u32 sys_reg, u64 new)
+static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
 {
 	u64 val = 0;
 	u64 strict_mask = ~0x0ULL;
@@ -863,7 +881,7 @@ static void __init init_cpu_hwcaps_indirect_list(void)
 
 static void __init setup_boot_cpu_capabilities(void);
 
-static void __init init_32bit_cpu_features(struct cpuinfo_32bit *info)
+static void init_32bit_cpu_features(struct cpuinfo_32bit *info)
 {
 	init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
 	init_cpu_ftr_reg(SYS_ID_DFR1_EL1, info->reg_id_dfr1);

arch/arm64/kernel/cpufeature.c static void relax_cpu_ftr_reg(u32 sys_id, int field) · line 1001

Quoted 5 lines
arch/arm64/kernel/cpufeature.c +2 -0
@@ -979,6 +997,22 @@ static void relax_cpu_ftr_reg(u32 sys_id, int field)
 	WARN_ON(!ftrp->width);
 }
 
+static void update_mismatched_32bit_el0_cpu_features(struct cpuinfo_arm64 *info,
+						     struct cpuinfo_arm64 *boot)

Could we s/update/lazy_init/ here?

Quoted 6 lines

IIUC this caters for the case where CPU0 doesn't have AArch32 but a secondary does. That, and the naming looks odd in update_cpu_features() when we have:

	update_mismatched_32bit_el0_cpu_features(...)
	update_32bit_cpu_features(...);

Sure thing, I'll rename this.

Quoted 31 lines

arch/arm64/kernel/cpufeature.c static void relax_cpu_ftr_reg(u32 sys_id, int field) · line 1014

Quoted 13 lines
arch/arm64/kernel/cpufeature.c +13 -0
+{
+	static bool boot_cpu_32bit_regs_overridden = false;
+
+	if (!allow_mismatched_32bit_el0 || boot_cpu_32bit_regs_overridden)
+		return;
+
+	if (id_aa64pfr0_32bit_el0(boot->reg_id_aa64pfr0))
+		return;
+
+	boot->aarch32 = info->aarch32;
+	init_32bit_cpu_features(&boot->aarch32);
+	boot_cpu_32bit_regs_overridden = true;
+}

Can't we share this with the boot CPU path if we do:

/*
* Initialize the common AArch32 features on the first CPU with AArch32. */
static void lazy_init_32bit_el0_cpu_features(struct cpuinfo_arm64 *info,
struct cpuinfo_arm64 *boot)

{
	static bool initialised = false;
	if (initialised || !id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
		return;
	
	boot->aarch32 = info->aarch32;
	init_32bit_cpu_features(&boot->aarch32);
	initiaised = true;
}
Quoted 2 lines

... or is the allow_mismatched_32bit_el0 check necessary for late hotplug?

Interesting. I think this works, but I'm wary that it results in the 32-bit features of a 64-bit-only boot CPU being populated using the first 32-bit-capable CPU even if we're not running with allow_mismatched_32bit_el0 enabled. That feels like setting ourselves up for future bugs. For example, compat_has_neon() would unexpectedly return true even though 32-bit execve() would be forbidden.

Quoted 6 lines
Quoted 6 lines
arch/arm64/kernel/cpufeature.c +6 -0
+static int __init init_32bit_el0_mask(void)
+{
+	if (!allow_mismatched_32bit_el0)
+		return 0;
+
+	if (!zalloc_cpumask_var(&cpu_32bit_el0_mask, GFP_KERNEL))
Quoted 8 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2903

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +6 -0
+		return -ENOMEM;
+
+	return cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+				 "arm64/mismatched_32bit_el0:online",
+				 enable_mismatched_32bit_el0, NULL);
+}

Shouldn't we clear this on a hot-unplug?

No, the mask is intended to show all of the 32-bit CPUs we've seen, so that userspace can use it to construct 32-bit-capable affinity masks. Having a race with hot-unplug doesn't help with that and sched_setaffinity() is quite happy with offline CPUs in the provided mask.

Additionally, the underlying mask is used to implement task_cpu_possible(), so if we wanted to remove CPUs then we'd need a separate mask just for sysfs (which _already_ exposes the online mask separately).

Will

reviewer
#7 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 45 lines · Will Deacon
Quoted 28 lines · Mark Rutland
Quoted 27 lines · Will Deacon

When confronted with a mixture of CPUs, some of which support 32-bit applications and others which don't, we quite sensibly treat the system as 64-bit only for userspace and prevent execve() of 32-bit binaries.

Unfortunately, some crazy folks have decided to build systems like this with the intention of running 32-bit applications, so relax our sanitisation logic to continue to advertise 32-bit support to userspace on these systems and track the real 32-bit capable cores in a cpumask instead. For now, the default behaviour remains but will be tied to a command-line option in a later patch.

---
 arch/arm64/include/asm/cpufeature.h |   8 +-
 arch/arm64/kernel/cpufeature.c      | 114 ++++++++++++++++++++++++----
 arch/arm64/tools/cpucaps            |   3 +-
 3 files changed, 110 insertions(+), 15 deletions(-)
arch/arm64/include/asm/cpufeature.h +0 -0
index 338840c00e8e..603bf4160cd6 100644
@@ -630,9 +630,15 @@ static inline bool cpu_supports_mixed_endian_el0(void)
 	return id_aa64mmfr0_mixed_endian_el0(read_cpuid(ID_AA64MMFR0_EL1));
 }
 
Quoted 15 lines

arch/arm64/include/asm/cpufeature.h static inline bool cpu_supports_mixed_endian_el0(void) · line 642

Quoted 11 lines
arch/arm64/include/asm/cpufeature.h +7 -1
+const struct cpumask *system_32bit_el0_cpumask(void);
+DECLARE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
 static inline bool system_supports_32bit_el0(void)
 {
-	return cpus_have_const_cap(ARM64_HAS_32BIT_EL0);
+	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	return static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       id_aa64pfr0_32bit_el0(pfr0);
 }

Note that read_sanitised_ftr_reg() has to do a bsearch() to find the arm64_ftr_reg, so this will make system_32bit_el0_cpumask() a fair amount more expensive than it needs to be.

Quoted 7 lines

I seriously doubt that it matters, but it did come up before and I proposed a potential solution if it's actually a concern:

https://lore.kernel.org/r/20201202172727.GC29813@willie-the-truck

so if you can show that it's a problem, we can resurrect something like that.

I'm happy to leave that for future. I raised this because elsewhere this is an issue when we need to avoid instrumentation; if that's not a concern here on any path then I am not aware of a functional issue.

Quoted 14 lines
Quoted 2 lines

Can we follow the pattern we have for arm64_ftr_reg_ctrel0, and have a arm64_ftr_reg_id_aa64pfr0_el1 that we can address directly here?

I mean, clearly its possible, but based on what data?

Quoted 9 lines

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

Quoted 8 lines
Quoted 4 lines

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))

Something similar was discussed in November last year but this falls apart with late onlining because its not generally possible to tell whether you've seen all the CPUs or not.

Ah; is that for when your boot CPU set is all AArch32-capable, but a late-onlined CPU is not?

I assume that we require at least one of the set of boot CPUs to be AArch32 cpable, and don't settle the compat hwcaps after userspace has started.

Quoted 3 lines

I'm mostly reluctant to make significant changes based on cosmetic preferences because testing and debugging this with all the system combinations is really difficult.

Fair enough.

Quoted 34 lines
Quoted 31 lines

arch/arm64/include/asm/cpufeature.h static inline bool cpu_supports_mixed_endian_el0(void)

Quoted 13 lines
arch/arm64/include/asm/cpufeature.h +13 -0
+{
+	static bool boot_cpu_32bit_regs_overridden = false;
+
+	if (!allow_mismatched_32bit_el0 || boot_cpu_32bit_regs_overridden)
+		return;
+
+	if (id_aa64pfr0_32bit_el0(boot->reg_id_aa64pfr0))
+		return;
+
+	boot->aarch32 = info->aarch32;
+	init_32bit_cpu_features(&boot->aarch32);
+	boot_cpu_32bit_regs_overridden = true;
+}

Can't we share this with the boot CPU path if we do:

/*
* Initialize the common AArch32 features on the first CPU with AArch32. */
static void lazy_init_32bit_el0_cpu_features(struct cpuinfo_arm64 *info,
struct cpuinfo_arm64 *boot)

{
	static bool initialised = false;
	if (initialised || !id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0))
		return;
	
	boot->aarch32 = info->aarch32;
	init_32bit_cpu_features(&boot->aarch32);
	initiaised = true;
}
Quoted 2 lines

... or is the allow_mismatched_32bit_el0 check necessary for late hotplug?

Quoted 6 lines

Interesting. I think this works, but I'm wary that it results in the 32-bit features of a 64-bit-only boot CPU being populated using the first 32-bit-capable CPU even if we're not running with allow_mismatched_32bit_el0 enabled. That feels like setting ourselves up for future bugs. For example, compat_has_neon() would unexpectedly return true even though 32-bit execve() would be forbidden.

Sure; I had not considered that case.

Thanks,
Mark.

author maintainer
#8 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 30 lines · Mark Rutland
Quoted 29 lines · Will Deacon
Quoted 9 lines · Mark Rutland
Quoted 8 lines · Will Deacon
arch/arm64/include/asm/cpufeature.h +0 -0
index 338840c00e8e..603bf4160cd6 100644
@@ -630,9 +630,15 @@ static inline bool cpu_supports_mixed_endian_el0(void)
 	return id_aa64mmfr0_mixed_endian_el0(read_cpuid(ID_AA64MMFR0_EL1));
 }
 
Quoted 15 lines

arch/arm64/include/asm/cpufeature.h static inline bool cpu_supports_mixed_endian_el0(void) · line 642

Quoted 11 lines
arch/arm64/include/asm/cpufeature.h +7 -1
+const struct cpumask *system_32bit_el0_cpumask(void);
+DECLARE_STATIC_KEY_FALSE(arm64_mismatched_32bit_el0);
+
 static inline bool system_supports_32bit_el0(void)
 {
-	return cpus_have_const_cap(ARM64_HAS_32BIT_EL0);
+	u64 pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	return static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       id_aa64pfr0_32bit_el0(pfr0);
 }

Note that read_sanitised_ftr_reg() has to do a bsearch() to find the arm64_ftr_reg, so this will make system_32bit_el0_cpumask() a fair amount more expensive than it needs to be.

I seriously doubt that it matters, but it did come up before and I proposed a potential solution if it's actually a concern:

Quoted 8 lines
Quoted 4 lines

https://lore.kernel.org/r/20201202172727.GC29813@willie-the-truck

so if you can show that it's a problem, we can resurrect something like that.

I'm happy to leave that for future. I raised this because elsewhere this is an issue when we need to avoid instrumentation; if that's not a concern here on any path then I am not aware of a functional issue.

I can't think of a reason why instrumentation would be an issue for any of the current callers, but that's a good point to bear in mind.

Quoted 18 lines
Quoted 9 lines
Quoted 9 lines

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

Quoted 8 lines
Quoted 4 lines

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))

Something similar was discussed in November last year but this falls apart with late onlining because its not generally possible to tell whether you've seen all the CPUs or not.

Quoted 6 lines

Ah; is that for when your boot CPU set is all AArch32-capable, but a late-onlined CPU is not?

I assume that we require at least one of the set of boot CPUs to be AArch32 cpable, and don't settle the compat hwcaps after userspace has started.

Heh, you assume wrong :)

When we allow the mismatch, then we do actually defer initialisation of the compat hwcaps until we see a 32-bit CPU. That's fine, as they won't be visible to userspace until then anyway (PER_LINUX32 is unavailable).

Will

reviewer
#9 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 31 lines · Will Deacon
Quoted 23 lines · Mark Rutland
Quoted 13 lines · Will Deacon
Quoted 12 lines · Mark Rutland

arch/arm64/include/asm/cpufeature.h

Quoted 1 line · Will Deacon
arch/arm64/include/asm/cpufeature.h +0 -0

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

Quoted 8 lines
Quoted 4 lines

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))

Something similar was discussed in November last year but this falls apart with late onlining because its not generally possible to tell whether you've seen all the CPUs or not.

Quoted 6 lines

Ah; is that for when your boot CPU set is all AArch32-capable, but a late-onlined CPU is not?

I assume that we require at least one of the set of boot CPUs to be AArch32 cpable, and don't settle the compat hwcaps after userspace has started.

Quoted 5 lines

Heh, you assume wrong :)

When we allow the mismatch, then we do actually defer initialisation of the compat hwcaps until we see a 32-bit CPU. That's fine, as they won't be visible to userspace until then anyway (PER_LINUX32 is unavailable).

That sounds quite scary, to me, though I don't have a concrete problem to hand. :/

Do we really need to support initializing that so late? For all other caps we've settled things when the boot CPUs come up, and it's unfortunate to have to treat this differently.

I'll go see if there's anything that's liable to break today.

Thanks,
Mark

author maintainer
#10 lore raw

Re: [PATCH v8 02/19] arm64: Allow mismatched 32-bit EL0 support

Quoted 38 lines · Mark Rutland
Quoted 31 lines · Will Deacon
Quoted 23 lines · Mark Rutland
Quoted 13 lines · Will Deacon
Quoted 12 lines · Mark Rutland

arch/arm64/include/asm/cpufeature.h

Quoted 1 line · Will Deacon
arch/arm64/include/asm/cpufeature.h +0 -0

That said. I reckon this could be much cleaner if we maintained separate caps:

ARM64_ALL_CPUS_HAVE_32BIT_EL0
ARM64_SOME_CPUS_HAVE_32BIT_EL0

... and allow arm64_mismatched_32bit_el0 to be set dependent on ARM64_SOME_CPUS_HAVE_32BIT_EL0. With that, this can be simplified to:

Quoted 8 lines
Quoted 4 lines

static inline bool system_supports_32bit_el0(void)

{
	return (cpus_have_const_cap(ARM64_ALL_CPUS_HAVE_32BIT_EL0)) ||
		static_branch_unlikely(&arm64_mismatched_32bit_el0))

Something similar was discussed in November last year but this falls apart with late onlining because its not generally possible to tell whether you've seen all the CPUs or not.

Quoted 6 lines

Ah; is that for when your boot CPU set is all AArch32-capable, but a late-onlined CPU is not?

I assume that we require at least one of the set of boot CPUs to be AArch32 cpable, and don't settle the compat hwcaps after userspace has started.

Quoted 5 lines

Heh, you assume wrong :)

When we allow the mismatch, then we do actually defer initialisation of the compat hwcaps until we see a 32-bit CPU. That's fine, as they won't be visible to userspace until then anyway (PER_LINUX32 is unavailable).

Quoted 6 lines

That sounds quite scary, to me, though I don't have a concrete problem to hand. :/

Do we really need to support initializing that so late? For all other caps we've settled things when the boot CPUs come up, and it's unfortunate to have to treat this differently.

I think it's the nature of the beast, unfortunately. Since we're talking about multiple generations of SoCs rather than just one oddball design, then placing artificial restrictions on the boot CPUs doesn't feel like it will last very long.

Quoted 1 line

I'll go see if there's anything that's liable to break today.

Please let me know if you find anything.

Will

author maintainer
#11 lore raw

PATCH v8 3/19 KVM: arm64: Kill 32-bit vCPUs on systems with mismatched EL0 support

In reply to Will Deacon

If a vCPU is caught running 32-bit code on a system with mismatched support at EL0, then we should kill it.

---
 arch/arm64/kvm/arm.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)
arch/arm64/kvm/arm.c +10 -1
index 1cb39c0803a4..5bdba97a7654 100644
@@ -692,6 +692,15 @@ static void check_vcpu_requests(struct kvm_vcpu *vcpu)
 	}
 }
 
+static bool vcpu_mode_is_bad_32bit(struct kvm_vcpu *vcpu)
+{
+	if (likely(!vcpu_mode_is_32bit(vcpu)))
+		return false;
+
+	return !system_supports_32bit_el0() ||
+		static_branch_unlikely(&arm64_mismatched_32bit_el0);
+}
+
 /**
  * kvm_arch_vcpu_ioctl_run - the main VCPU run function to execute guest code
  * @vcpu:	The VCPU pointer
@@ -875,7 +884,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 		 * with the asymmetric AArch32 case), return to userspace with
 		 * a fatal error.
 		 */
-		if (!system_supports_32bit_el0() && vcpu_mode_is_32bit(vcpu)) {
+		if (vcpu_mode_is_bad_32bit(vcpu)) {
 			/*
 			 * As we have caught the guest red-handed, decide that
 			 * it isn't fit for purpose anymore by making the vcpu
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#12 lore raw

PATCH v8 4/19 arm64: Kill 32-bit applications scheduled on 64-bit-only CPUs

In reply to Will Deacon

Scheduling a 32-bit application on a 64-bit-only CPU is a bad idea.

Ensure that 32-bit applications always take the slow-path when returning to userspace on a system with mismatched support at EL0, so that we can avoid trying to run on a 64-bit-only CPU and force a SIGKILL instead.

---
 arch/arm64/kernel/process.c | 19 ++++++++++++++++++-
 arch/arm64/kernel/signal.c  | 26 ++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 1 deletion(-)
arch/arm64/kernel/process.c +18 -1
index b4bb67f17a2c..f4a91bf1ce0c 100644
@@ -527,6 +527,15 @@ static void erratum_1418040_thread_switch(struct task_struct *prev,
 	write_sysreg(val, cntkctl_el1);
 }
 
+static void compat_thread_switch(struct task_struct *next)
+{
+	if (!is_compat_thread(task_thread_info(next)))
+		return;
+
+	if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
+		set_tsk_thread_flag(next, TIF_NOTIFY_RESUME);
+}
+
 static void update_sctlr_el1(u64 sctlr)
 {
 	/*
@@ -568,6 +577,7 @@ __notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
 	ssbs_thread_switch(next);
 	erratum_1418040_thread_switch(prev, next);
 	ptrauth_thread_switch_user(next);
+	compat_thread_switch(next);
 
 	/*
 	 * Complete any pending TLB or cache maintenance on this CPU in case
@@ -633,8 +643,15 @@ unsigned long arch_align_stack(unsigned long sp)
  */
 void arch_setup_new_exec(void)
 {
-	current->mm->context.flags = is_compat_task() ? MMCF_AARCH32 : 0;
+	unsigned long mmflags = 0;
+
+	if (is_compat_task()) {
+		mmflags = MMCF_AARCH32;
+		if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
+			set_tsk_thread_flag(current, TIF_NOTIFY_RESUME);
+	}
 
+	current->mm->context.flags = mmflags;
 	ptrauth_thread_init_user();
 	mte_thread_init_user();
 
arch/arm64/kernel/signal.c +26 -0
index 6237486ff6bb..f8192f4ae0b8 100644
@@ -911,6 +911,19 @@ static void do_signal(struct pt_regs *regs)
 	restore_saved_sigmask();
 }
 
+static bool cpu_affinity_invalid(struct pt_regs *regs)
+{
+	if (!compat_user_mode(regs))
+		return false;
+
+	/*
+	 * We're preemptible, but a reschedule will cause us to check the
+	 * affinity again.
+	 */
+	return !cpumask_test_cpu(raw_smp_processor_id(),
+				 system_32bit_el0_cpumask());
+}
+
 asmlinkage void do_notify_resume(struct pt_regs *regs,
 				 unsigned long thread_flags)
 {
@@ -938,6 +951,19 @@ asmlinkage void do_notify_resume(struct pt_regs *regs,
 			if (thread_flags & _TIF_NOTIFY_RESUME) {
 				tracehook_notify_resume(regs);
 				rseq_handle_notify_resume(NULL, regs);
+
+				/*
+				 * If we reschedule after checking the affinity
+				 * then we must ensure that TIF_NOTIFY_RESUME
+				 * is set so that we check the affinity again.
+				 * Since tracehook_notify_resume() clears the
+				 * flag, ensure that the compiler doesn't move
+				 * it after the affinity check.
+				 */
+				barrier();
+
+				if (cpu_affinity_invalid(regs))
+					force_sig(SIGKILL);
 			}
 
 			if (thread_flags & _TIF_FOREIGN_FPSTATE)
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#13 lore raw

PATCH v8 5/19 sched: Introduce task_cpu_possible_mask() to limit fallback rq selection

In reply to Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

On such a system, we must take care not to migrate a task to an unsupported CPU when forcefully moving tasks in select_fallback_rq() in response to a CPU hot-unplug operation.

Introduce a task_cpu_possible_mask() hook which, given a task argument, allows an architecture to return a cpumask of CPUs that are capable of executing that task. The default implementation returns the cpu_possible_mask, since sane machines do not suffer from per-cpu ISA limitations that affect scheduling. The new mask is used when selecting the fallback runqueue as a last resort before forcing a migration to the first active CPU.

---
 include/linux/mmu_context.h | 11 +++++++++++
 kernel/sched/core.c         |  5 ++---
 2 files changed, 13 insertions(+), 3 deletions(-)
include/linux/mmu_context.h +11 -0
index 03dee12d2b61..1a599ba3524f 100644
@@ -14,4 +14,15 @@
 static inline void leave_mm(int cpu) { }
 #endif
 
+/*
+ * CPUs that are capable of running task @p. By default, we assume a sane,
+ * homogeneous system. Must contain at least one active CPU.
+ */
+#ifndef task_cpu_possible_mask
+# define task_cpu_possible_mask(p)	cpu_possible_mask
+# define task_cpu_possible(cpu, p)	true
+#else
+# define task_cpu_possible(cpu, p)	cpumask_test_cpu((cpu), task_cpu_possible_mask(p))
+#endif
+
 #endif
kernel/sched/core.c +2 -3
index 5226cc26a095..0c1b6f1a6c91 100644
@@ -1814,7 +1814,7 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
 
 	/* Non kernel threads are not allowed during either online or offline. */
 	if (!(p->flags & PF_KTHREAD))
-		return cpu_active(cpu);
+		return cpu_active(cpu) && task_cpu_possible(cpu, p);
 
 	/* KTHREAD_IS_PER_CPU is always allowed. */
 	if (kthread_is_per_cpu(p))
@@ -2792,10 +2792,9 @@ static int select_fallback_rq(int cpu, struct task_struct *p)
 			 *
 			 * More yuck to audit.
 			 */
-			do_set_cpus_allowed(p, cpu_possible_mask);
+			do_set_cpus_allowed(p, task_cpu_possible_mask(p));
 			state = fail;
 			break;
-
 		case fail:
 			BUG();
 			break;
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 5/19
#14 lore raw

Re: [PATCH v8 05/19] sched: Introduce task_cpu_possible_mask() to limit fallback rq selection

Quoted 9 lines · Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

On such a system, we must take care not to migrate a task to an unsupported CPU when forcefully moving tasks in select_fallback_rq() in response to a CPU hot-unplug operation.

Quoted 8 lines

Introduce a task_cpu_possible_mask() hook which, given a task argument, allows an architecture to return a cpumask of CPUs that are capable of executing that task. The default implementation returns the cpu_possible_mask, since sane machines do not suffer from per-cpu ISA limitations that affect scheduling. The new mask is used when selecting the fallback runqueue as a last resort before forcing a migration to the first active CPU.

Nit: Some uses of this mask (cpu_is_allowed(), __set_cpus_allowed_ptr()) don't apply to kthreads. This makes sense for the 32-bit@EL0 faff, but it wouldn't hurt to point this out somewhere IMO.

Also, that's an odd place for the definitions, but IIRC there isn't a much better choice.

author maintainer
#15 lore raw

Re: [PATCH v8 05/19] sched: Introduce task_cpu_possible_mask() to limit fallback rq selection

Quoted 19 lines · Valentin Schneider
Quoted 9 lines · Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

On such a system, we must take care not to migrate a task to an unsupported CPU when forcefully moving tasks in select_fallback_rq() in response to a CPU hot-unplug operation.

Quoted 8 lines

Introduce a task_cpu_possible_mask() hook which, given a task argument, allows an architecture to return a cpumask of CPUs that are capable of executing that task. The default implementation returns the cpu_possible_mask, since sane machines do not suffer from per-cpu ISA limitations that affect scheduling. The new mask is used when selecting the fallback runqueue as a last resort before forcing a migration to the first active CPU.

Quoted 3 lines

Nit: Some uses of this mask (cpu_is_allowed(), __set_cpus_allowed_ptr()) don't apply to kthreads. This makes sense for the 32-bit@EL0 faff, but it wouldn't hurt to point this out somewhere IMO.

That's a good point: even after these patches, we still assume the kernel (and therefore kthreads) can run on all CPUs. I'll expand the comment.

Quoted 2 lines

Also, that's an odd place for the definitions, but IIRC there isn't a much better choice.

Short of adding a new header just for this, I couldn't find anything, no.

Quoted 1 line

Thanks!

Will

author maintainer
#16 lore raw

PATCH v8 6/19 cpuset: Don't use the cpu_possible_mask as a last resort for cgroup v1

In reply to Will Deacon

If the scheduler cannot find an allowed CPU for a task, cpuset_cpus_allowed_fallback() will widen the affinity to cpu_possible_mask if cgroup v1 is in use.

In preparation for allowing architectures to provide their own fallback mask, just return early if we're either using cgroup v1 or we're using cgroup v2 with a mask that contains invalid CPUs. This will allow select_fallback_rq() to figure out the mask by itself.

---
 include/linux/cpuset.h | 1 +
 kernel/cgroup/cpuset.c | 8 ++++++--
 2 files changed, 7 insertions(+), 2 deletions(-)
include/linux/cpuset.h +1 -0
index 04c20de66afc..ed6ec677dd6b 100644
@@ -15,6 +15,7 @@
 #include <linux/cpumask.h>
 #include <linux/nodemask.h>
 #include <linux/mm.h>
+#include <linux/mmu_context.h>
 #include <linux/jump_label.h>
 
 #ifdef CONFIG_CPUSETS
kernel/cgroup/cpuset.c +6 -2
index a945504c0ae7..6ec7303d5b1f 100644
@@ -3322,9 +3322,13 @@ void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask)
 
 void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 {
+	const struct cpumask *cs_mask;
+	const struct cpumask *possible_mask = task_cpu_possible_mask(tsk);
+
 	rcu_read_lock();
-	do_set_cpus_allowed(tsk, is_in_v2_mode() ?
-		task_cs(tsk)->cpus_allowed : cpu_possible_mask);
+	cs_mask = task_cs(tsk)->cpus_allowed;
+	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask))
+		do_set_cpus_allowed(tsk, cs_mask);
 	rcu_read_unlock();
 
 	/*
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
#17 lore raw

Re: [PATCH v8 06/19] cpuset: Don't use the cpu_possible_mask as a last resort for cgroup v1

kernel/cgroup/cpuset.c void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask) · line 3331

Quoted 13 lines · Will Deacon
kernel/cgroup/cpuset.c +6 -2
@@ -3322,9 +3322,13 @@ void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask)
 
 void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 {
+	const struct cpumask *cs_mask;
+	const struct cpumask *possible_mask = task_cpu_possible_mask(tsk);
+
      rcu_read_lock();
-	do_set_cpus_allowed(tsk, is_in_v2_mode() ?
-		task_cs(tsk)->cpus_allowed : cpu_possible_mask);
+	cs_mask = task_cs(tsk)->cpus_allowed;
+	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask))
+		do_set_cpus_allowed(tsk, cs_mask);

Since the task will still go through the is_cpu_allowed() loop in select_fallback_rq() after this, is the subset check actually required here?

It would have more merit if cpuset_cpus_allowed_fallback() returned whether it actually changed the allowed mask or not, in which case we could branch either to the is_cpu_allowed() loop (as we do unconditionally now), or to the 'state == possible' switch case.

Quoted 5 lines
kernel/cgroup/cpuset.c +0 -1
      rcu_read_unlock();
 
      /*
--

2.32.0.rc0.204.g9fa02ecfa5-goog

author maintainer
#18 lore raw

Re: [PATCH v8 06/19] cpuset: Don't use the cpu_possible_mask as a last resort for cgroup v1

Quoted 18 lines · Valentin Schneider

Review void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask) · line 3331

Quoted 13 lines · Will Deacon
(quoted diff) +6 -2
@@ -3322,9 +3322,13 @@ void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask)
 
 void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 {
+	const struct cpumask *cs_mask;
+	const struct cpumask *possible_mask = task_cpu_possible_mask(tsk);
+
      rcu_read_lock();
-	do_set_cpus_allowed(tsk, is_in_v2_mode() ?
-		task_cs(tsk)->cpus_allowed : cpu_possible_mask);
+	cs_mask = task_cs(tsk)->cpus_allowed;
+	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask))
+		do_set_cpus_allowed(tsk, cs_mask);

Since the task will still go through the is_cpu_allowed() loop in select_fallback_rq() after this, is the subset check actually required here?

Yes, I think it's needed. do_set_cpus_allowed() doesn't do any checking against the task_cpu_possible_mask, so if we returned to select_fallback_rq() with a mask containing a mixture of 32-bit-capable and 64-bit-only CPUs then we'd end up setting an affinity mask for a 32-bit task which contains 64-bit-only cores.

Quoted 4 lines

It would have more merit if cpuset_cpus_allowed_fallback() returned whether it actually changed the allowed mask or not, in which case we could branch either to the is_cpu_allowed() loop (as we do unconditionally now), or to the 'state == possible' switch case.

I think this is a cleanup, so I can include it as a separate patch (see below).

Will

--->8

include/linux/cpuset.h +3 -2
index 414a8e694413..d2b9c41c8edf 100644
@@ -59,7 +59,7 @@ extern void cpuset_wait_for_hotplug(void);
 extern void cpuset_read_lock(void);
 extern void cpuset_read_unlock(void);
 extern void cpuset_cpus_allowed(struct task_struct *p, struct cpumask *mask);
-extern void cpuset_cpus_allowed_fallback(struct task_struct *p);
+extern bool cpuset_cpus_allowed_fallback(struct task_struct *p);
 extern nodemask_t cpuset_mems_allowed(struct task_struct *p);
 #define cpuset_current_mems_allowed (current->mems_allowed)
 void cpuset_init_current_mems_allowed(void);
@@ -188,8 +188,9 @@ static inline void cpuset_cpus_allowed(struct task_struct *p,
        cpumask_copy(mask, task_cpu_possible_mask(p));
 }
 
-static inline void cpuset_cpus_allowed_fallback(struct task_struct *p)
+static inline bool cpuset_cpus_allowed_fallback(struct task_struct *p)
 {
+       return false;
 }
 
 static inline nodemask_t cpuset_mems_allowed(struct task_struct *p)
kernel/cgroup/cpuset.c +8 -2
index 4e7c271e3800..a6bab2259f98 100644
@@ -3327,17 +3327,22 @@ void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask)
  * which will not contain a sane cpumask during cases such as cpu hotplugging.
  * This is the absolute last resort for the scheduler and it is only used if
  * _every_ other avenue has been traveled.
+ *
+ * Returns true if the affinity of @tsk was changed, false otherwise.
  **/
 
-void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
+bool cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 {
        const struct cpumask *cs_mask;
+       bool changed = false;
        const struct cpumask *possible_mask = task_cpu_possible_mask(tsk);
 
        rcu_read_lock();
        cs_mask = task_cs(tsk)->cpus_allowed;
-       if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask))
+       if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask)) {
                do_set_cpus_allowed(tsk, cs_mask);
+               changed = true;
+       }
        rcu_read_unlock();
 
        /*
@@ -3357,6 +3362,7 @@ void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
         * select_fallback_rq() will fix things ups and set cpu_possible_mask
         * if required.
         */
+       return changed;
 }
 
 void __init cpuset_init_current_mems_allowed(void)
kernel/sched/core.c +1 -2
index fc7de4f955cf..9d7a74a07632 100644
@@ -2951,8 +2951,7 @@ static int select_fallback_rq(int cpu, struct task_struct *p)
                /* No more Mr. Nice Guy. */
                switch (state) {
                case cpuset:
-                       if (IS_ENABLED(CONFIG_CPUSETS)) {
-                               cpuset_cpus_allowed_fallback(p);
+                       if (cpuset_cpus_allowed_fallback(p)) {
                                state = possible;
                                break;
                        }
#19 lore raw

Re: [PATCH v8 06/19] cpuset: Don't use the cpu_possible_mask as a last resort for cgroup v1

Quoted 20 lines · Will Deacon
Quoted 18 lines · Valentin Schneider

Review void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask) · line 3331

Quoted 13 lines · Will Deacon
(quoted diff) +6 -2
@@ -3322,9 +3322,13 @@ void cpuset_cpus_allowed(struct task_struct *tsk, struct cpumask *pmask)
 
 void cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 {
+	const struct cpumask *cs_mask;
+	const struct cpumask *possible_mask = task_cpu_possible_mask(tsk);
+
      rcu_read_lock();
-	do_set_cpus_allowed(tsk, is_in_v2_mode() ?
-		task_cs(tsk)->cpus_allowed : cpu_possible_mask);
+	cs_mask = task_cs(tsk)->cpus_allowed;
+	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask))
+		do_set_cpus_allowed(tsk, cs_mask);

Since the task will still go through the is_cpu_allowed() loop in select_fallback_rq() after this, is the subset check actually required here?

Quoted 6 lines

Yes, I think it's needed. do_set_cpus_allowed() doesn't do any checking against the task_cpu_possible_mask, so if we returned to select_fallback_rq() with a mask containing a mixture of 32-bit-capable and 64-bit-only CPUs then we'd end up setting an affinity mask for a 32-bit task which contains 64-bit-only cores.

Once again, you're right :-)

author maintainer
#20 lore raw

PATCH v8 7/19 cpuset: Honour task_cpu_possible_mask() in guarantee_online_cpus()

In reply to Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

Modify guarantee_online_cpus() to take task_cpu_possible_mask() into account when trying to find a suitable set of online CPUs for a given task. This will avoid passing an invalid mask to set_cpus_allowed_ptr() during ->attach() and will subsequently allow the cpuset hierarchy to be taken into account when forcefully overriding the affinity mask for a task which requires migration to a compatible CPU.

Cc Li Zefan <[email protected]>
Cc Tejun Heo <[email protected]>
Cc Johannes Weiner <[email protected]>
---
 include/linux/cpuset.h |  2 +-
 kernel/cgroup/cpuset.c | 41 ++++++++++++++++++++++++++---------------
 2 files changed, 27 insertions(+), 16 deletions(-)
include/linux/cpuset.h +1 -1
index ed6ec677dd6b..414a8e694413 100644
@@ -185,7 +185,7 @@ static inline void cpuset_read_unlock(void) { }
 static inline void cpuset_cpus_allowed(struct task_struct *p,
 				       struct cpumask *mask)
 {
-	cpumask_copy(mask, cpu_possible_mask);
+	cpumask_copy(mask, task_cpu_possible_mask(p));
 }
 
 static inline void cpuset_cpus_allowed_fallback(struct task_struct *p)
kernel/cgroup/cpuset.c +26 -15

77 lines not on this page · raw message

Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 7/19
#21 lore raw

Re: [PATCH v8 07/19] cpuset: Honour task_cpu_possible_mask() in guarantee_online_cpus()

Quoted 12 lines · Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

Modify guarantee_online_cpus() to take task_cpu_possible_mask() into account when trying to find a suitable set of online CPUs for a given task. This will avoid passing an invalid mask to set_cpus_allowed_ptr() during ->attach() and will subsequently allow the cpuset hierarchy to be taken into account when forcefully overriding the affinity mask for a task which requires migration to a compatible CPU.

Quoted 4 lines
Cc Li Zefan <[email protected]>
Cc Tejun Heo <[email protected]>
Cc Johannes Weiner <[email protected]>
author maintainer
#22 lore raw

PATCH v8 8/19 sched: Reject CPU affinity changes based on task_cpu_possible_mask()

In reply to Will Deacon

Reject explicit requests to change the affinity mask of a task via set_cpus_allowed_ptr() if the requested mask is not a subset of the mask returned by task_cpu_possible_mask(). This ensures that the 'cpus_mask' for a given task cannot contain CPUs which are incapable of executing it, except in cases where the affinity is forced.

---
 kernel/sched/core.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
kernel/sched/core.c +8 -1
index 0c1b6f1a6c91..b23c7f0ab31a 100644
@@ -2347,15 +2347,17 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
 				  u32 flags)
 {
 	const struct cpumask *cpu_valid_mask = cpu_active_mask;
+	const struct cpumask *cpu_allowed_mask = task_cpu_possible_mask(p);
 	unsigned int dest_cpu;
 	struct rq_flags rf;
 	struct rq *rq;
 	int ret = 0;
+	bool kthread = p->flags & PF_KTHREAD;
 
 	rq = task_rq_lock(p, &rf);
 	update_rq_clock(rq);
 
-	if (p->flags & PF_KTHREAD || is_migration_disabled(p)) {
+	if (kthread || is_migration_disabled(p)) {
 		/*
 		 * Kernel threads are allowed on online && !active CPUs,
 		 * however, during cpu-hot-unplug, even these might get pushed
@@ -2369,6 +2371,11 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
 		cpu_valid_mask = cpu_online_mask;
 	}
 
+	if (!kthread && !cpumask_subset(new_mask, cpu_allowed_mask)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
 	/*
 	 * Must re-check here, to close a race against __kthread_bind(),
 	 * sched_setaffinity() is not guaranteed to observe the flag.
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 8/19
#23 lore raw

Re: [PATCH v8 08/19] sched: Reject CPU affinity changes based on task_cpu_possible_mask()

Quoted 8 lines · Will Deacon

Reject explicit requests to change the affinity mask of a task via set_cpus_allowed_ptr() if the requested mask is not a subset of the mask returned by task_cpu_possible_mask(). This ensures that the 'cpus_mask' for a given task cannot contain CPUs which are incapable of executing it, except in cases where the affinity is forced.

One comment/observation below, but regardless:

Quoted 28 lines
---
 kernel/sched/core.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
kernel/sched/core.c +3 -1
index 0c1b6f1a6c91..b23c7f0ab31a 100644
@@ -2347,15 +2347,17 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
                                u32 flags)
 {
      const struct cpumask *cpu_valid_mask = cpu_active_mask;
+	const struct cpumask *cpu_allowed_mask = task_cpu_possible_mask(p);
      unsigned int dest_cpu;
      struct rq_flags rf;
      struct rq *rq;
      int ret = 0;
+	bool kthread = p->flags & PF_KTHREAD;
 
      rq = task_rq_lock(p, &rf);
      update_rq_clock(rq);
 
-	if (p->flags & PF_KTHREAD || is_migration_disabled(p)) {
+	if (kthread || is_migration_disabled(p)) {
              /*
               * Kernel threads are allowed on online && !active CPUs,
               * however, during cpu-hot-unplug, even these might get pushed

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2378

Quoted 8 lines
kernel/sched/core.c +5 -0
@@ -2369,6 +2371,11 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
              cpu_valid_mask = cpu_online_mask;
      }
 
+	if (!kthread && !cpumask_subset(new_mask, cpu_allowed_mask)) {
+		ret = -EINVAL;
+		goto out;
+	}
+

IIUC this wouldn't be required if guarantee_online_cpus() couldn't build a mask that extends beyond task_cpu_possible_mask(p): if the new mask doesn't intersect with that possible mask, it means we're carrying an empty cpumask and the cpumask_any_and_distribute() below would return nr_cpu_ids, so we'd bail with -EINVAL.

I don't really see a way around it though due to the expectations behind guarantee_online_cpus() :/

Quoted 5 lines
kernel/sched/core.c +0 -1
      /*
       * Must re-check here, to close a race against __kthread_bind(),
       * sched_setaffinity() is not guaranteed to observe the flag.
--

2.32.0.rc0.204.g9fa02ecfa5-goog

author maintainer
#24 lore raw

Re: [PATCH v8 08/19] sched: Reject CPU affinity changes based on task_cpu_possible_mask()

Quoted 7 lines · Valentin Schneider
Quoted 6 lines · Will Deacon

Reject explicit requests to change the affinity mask of a task via set_cpus_allowed_ptr() if the requested mask is not a subset of the mask returned by task_cpu_possible_mask(). This ensures that the 'cpus_mask' for a given task cannot contain CPUs which are incapable of executing it, except in cases where the affinity is forced.

Quoted 6 lines
Quoted 2 lines

One comment/observation below, but regardless:

Thanks!

Quoted 39 lines
Quoted 24 lines
kernel/sched/core.c +3 -1
index 0c1b6f1a6c91..b23c7f0ab31a 100644
@@ -2347,15 +2347,17 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
                                u32 flags)
 {
      const struct cpumask *cpu_valid_mask = cpu_active_mask;
+	const struct cpumask *cpu_allowed_mask = task_cpu_possible_mask(p);
      unsigned int dest_cpu;
      struct rq_flags rf;
      struct rq *rq;
      int ret = 0;
+	bool kthread = p->flags & PF_KTHREAD;
 
      rq = task_rq_lock(p, &rf);
      update_rq_clock(rq);
 
-	if (p->flags & PF_KTHREAD || is_migration_disabled(p)) {
+	if (kthread || is_migration_disabled(p)) {
              /*
               * Kernel threads are allowed on online && !active CPUs,
               * however, during cpu-hot-unplug, even these might get pushed

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2378

Quoted 8 lines
kernel/sched/core.c +5 -0
@@ -2369,6 +2371,11 @@ static int __set_cpus_allowed_ptr(struct task_struct *p,
              cpu_valid_mask = cpu_online_mask;
      }
 
+	if (!kthread && !cpumask_subset(new_mask, cpu_allowed_mask)) {
+		ret = -EINVAL;
+		goto out;
+	}
+

IIUC this wouldn't be required if guarantee_online_cpus() couldn't build a mask that extends beyond task_cpu_possible_mask(p): if the new mask doesn't intersect with that possible mask, it means we're carrying an empty cpumask and the cpumask_any_and_distribute() below would return nr_cpu_ids, so we'd bail with -EINVAL.

Quoted 2 lines

I don't really see a way around it though due to the expectations behind guarantee_online_cpus() :/

Mostly agreed. I started out hacking SCA and only then started knocking the callers on the head. However, given how many callers there are for this thing, I'm much more comfortable having the mask check there to ensure that we return an error if the requested mask contains CPUs on which we're unable to run, and yes, guarantee_online_cpus() is one such caller.

Will

author maintainer
#25 lore raw

PATCH v8 9/19 sched: Introduce task_struct::user_cpus_ptr to track requested affinity

In reply to Will Deacon

In preparation for saving and restoring the user-requested CPU affinity mask of a task, add a new cpumask_t pointer to 'struct task_struct'.

If the pointer is non-NULL, then the mask is copied across fork() and freed on task exit.

---
 include/linux/sched.h | 13 +++++++++++++
 init/init_task.c      |  1 +
 kernel/fork.c         |  2 ++
 kernel/sched/core.c   | 20 ++++++++++++++++++++
 4 files changed, 36 insertions(+)
include/linux/sched.h +13 -0
index d2c881384517..db32d4f7e5b3 100644
@@ -730,6 +730,7 @@ struct task_struct {
 	unsigned int			policy;
 	int				nr_cpus_allowed;
 	const cpumask_t			*cpus_ptr;
+	cpumask_t			*user_cpus_ptr;
 	cpumask_t			cpus_mask;
 	void				*migration_pending;
 #ifdef CONFIG_SMP
@@ -1688,6 +1689,8 @@ extern int task_can_attach(struct task_struct *p, const struct cpumask *cs_cpus_
 #ifdef CONFIG_SMP
 extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask);
 extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
+extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
+extern void release_user_cpus_ptr(struct task_struct *p);
 #else
 static inline void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
 {
@@ -1698,6 +1701,16 @@ static inline int set_cpus_allowed_ptr(struct task_struct *p, const struct cpuma
 		return -EINVAL;
 	return 0;
 }
+static inline int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node)
+{
+	if (src->user_cpus_ptr)
+		return -EINVAL;
+	return 0;
+}
+static inline void release_user_cpus_ptr(struct task_struct *p)
+{
+	WARN_ON(p->user_cpus_ptr);
+}
 #endif
 
 extern int yield_to(struct task_struct *p, bool preempt);
init/init_task.c +1 -0
index 8b08c2e19cbb..158c2b1689e1 100644
@@ -80,6 +80,7 @@ struct task_struct init_task
 	.normal_prio	= MAX_PRIO - 20,
 	.policy		= SCHED_NORMAL,
 	.cpus_ptr	= &init_task.cpus_mask,
+	.user_cpus_ptr	= NULL,
 	.cpus_mask	= CPU_MASK_ALL,
 	.nr_cpus_allowed= NR_CPUS,
 	.mm		= NULL,
kernel/fork.c +2 -0
index dc06afd725cb..d3710e7f1686 100644
@@ -446,6 +446,7 @@ void put_task_stack(struct task_struct *tsk)
 
 void free_task(struct task_struct *tsk)
 {
+	release_user_cpus_ptr(tsk);
 	scs_release(tsk);
 
 #ifndef CONFIG_THREAD_INFO_IN_TASK
@@ -918,6 +919,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
 #endif
 	if (orig->cpus_ptr == &orig->cpus_mask)
 		tsk->cpus_ptr = &tsk->cpus_mask;
+	dup_user_cpus_ptr(tsk, orig, node);
 
 	/*
 	 * One for the user space visible state that goes away when reaped.
kernel/sched/core.c +20 -0
index b23c7f0ab31a..206e7bf95c79 100644
@@ -2126,6 +2126,26 @@ void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
 	__do_set_cpus_allowed(p, new_mask, 0);
 }
 
+int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src,
+		      int node)
+{
+	if (!src->user_cpus_ptr)
+		return 0;
+
+	dst->user_cpus_ptr = kmalloc_node(cpumask_size(), GFP_KERNEL, node);
+	if (!dst->user_cpus_ptr)
+		return -ENOMEM;
+
+	cpumask_copy(dst->user_cpus_ptr, src->user_cpus_ptr);
+	return 0;
+}
+
+void release_user_cpus_ptr(struct task_struct *p)
+{
+	kfree(p->user_cpus_ptr);
+	p->user_cpus_ptr = NULL;
+}
+
 /*
  * This function is wildly self concurrent; here be dragons.
  *
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 9/19
#26 lore raw

Re: [PATCH v8 09/19] sched: Introduce task_struct::user_cpus_ptr to track requested affinity

Quoted 7 lines · Will Deacon

In preparation for saving and restoring the user-requested CPU affinity mask of a task, add a new cpumask_t pointer to 'struct task_struct'.

If the pointer is non-NULL, then the mask is copied across fork() and freed on task exit.

author maintainer
#27 lore raw

PATCH v8 10/19 sched: Split the guts of sched_setaffinity() into a helper function

In reply to Will Deacon

In preparation for replaying user affinity requests using a saved mask, split sched_setaffinity() up so that the initial task lookup and security checks are only performed when the request is coming directly from userspace.

---
 kernel/sched/core.c | 105 ++++++++++++++++++++++++--------------------
 1 file changed, 57 insertions(+), 48 deletions(-)
kernel/sched/core.c +57 -48
index 206e7bf95c79..f210d0b06944 100644
@@ -6788,53 +6788,22 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr,
 	return retval;
 }
 
-long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
+static int
+__sched_setaffinity(struct task_struct *p, const struct cpumask *mask)
 {
-	cpumask_var_t cpus_allowed, new_mask;
-	struct task_struct *p;
 	int retval;
+	cpumask_var_t cpus_allowed, new_mask;
 
-	rcu_read_lock();
-
-	p = find_process_by_pid(pid);
-	if (!p) {
-		rcu_read_unlock();
-		return -ESRCH;
-	}
-
-	/* Prevent p going away */
-	get_task_struct(p);
-	rcu_read_unlock();
+	if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL))
+		return -ENOMEM;
 
-	if (p->flags & PF_NO_SETAFFINITY) {
-		retval = -EINVAL;
-		goto out_put_task;
-	}
-	if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) {
-		retval = -ENOMEM;
-		goto out_put_task;
-	}
 	if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) {
 		retval = -ENOMEM;
 		goto out_free_cpus_allowed;
 	}
-	retval = -EPERM;
-	if (!check_same_owner(p)) {
-		rcu_read_lock();
-		if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE)) {
-			rcu_read_unlock();
-			goto out_free_new_mask;
-		}
-		rcu_read_unlock();
-	}
-
-	retval = security_task_setscheduler(p);
-	if (retval)
-		goto out_free_new_mask;
-
 
 	cpuset_cpus_allowed(p, cpus_allowed);
-	cpumask_and(new_mask, in_mask, cpus_allowed);
+	cpumask_and(new_mask, mask, cpus_allowed);
 
 	/*
 	 * Since bandwidth control happens on root_domain basis,
@@ -6855,23 +6824,63 @@ long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
 #endif
 again:
 	retval = __set_cpus_allowed_ptr(p, new_mask, SCA_CHECK);
+	if (retval)
+		goto out_free_new_mask;
 
-	if (!retval) {
-		cpuset_cpus_allowed(p, cpus_allowed);
-		if (!cpumask_subset(new_mask, cpus_allowed)) {
-			/*
-			 * We must have raced with a concurrent cpuset
-			 * update. Just reset the cpus_allowed to the
-			 * cpuset's cpus_allowed
-			 */
-			cpumask_copy(new_mask, cpus_allowed);
-			goto again;
-		}
+	cpuset_cpus_allowed(p, cpus_allowed);
+	if (!cpumask_subset(new_mask, cpus_allowed)) {
+		/*
+		 * We must have raced with a concurrent cpuset update.
+		 * Just reset the cpumask to the cpuset's cpus_allowed.
+		 */
+		cpumask_copy(new_mask, cpus_allowed);
+		goto again;
 	}
+
 out_free_new_mask:
 	free_cpumask_var(new_mask);
 out_free_cpus_allowed:
 	free_cpumask_var(cpus_allowed);
+	return retval;
+}
+
+long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
+{
+	struct task_struct *p;
+	int retval;
+
+	rcu_read_lock();
+
+	p = find_process_by_pid(pid);
+	if (!p) {
+		rcu_read_unlock();
+		return -ESRCH;
+	}
+
+	/* Prevent p going away */
+	get_task_struct(p);
+	rcu_read_unlock();
+
+	if (p->flags & PF_NO_SETAFFINITY) {
+		retval = -EINVAL;
+		goto out_put_task;
+	}
+
+	if (!check_same_owner(p)) {
+		rcu_read_lock();
+		if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE)) {
+			rcu_read_unlock();
+			retval = -EPERM;
+			goto out_put_task;
+		}
+		rcu_read_unlock();
+	}
+
+	retval = security_task_setscheduler(p);
+	if (retval)
+		goto out_put_task;
+
+	retval = __sched_setaffinity(p, in_mask);
 out_put_task:
 	put_task_struct(p);
 	return retval;
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 10/19
#28 lore raw

Re: [PATCH v8 10/19] sched: Split the guts of sched_setaffinity() into a helper function

Quoted 6 lines · Will Deacon

In preparation for replaying user affinity requests using a saved mask, split sched_setaffinity() up so that the initial task lookup and security checks are only performed when the request is coming directly from userspace.

author maintainer
#29 lore raw

PATCH v8 11/19 sched: Allow task CPU affinity to be restricted on asymmetric systems

In reply to Will Deacon

Asymmetric systems may not offer the same level of userspace ISA support across all CPUs, meaning that some applications cannot be executed by some CPUs. As a concrete example, upcoming arm64 big.LITTLE designs do not feature support for 32-bit applications on both clusters.

Although userspace can carefully manage the affinity masks for such tasks, one place where it is particularly problematic is execve() because the CPU on which the execve() is occurring may be incompatible with the new application image. In such a situation, it is desirable to restrict the affinity mask of the task and ensure that the new image is entered on a compatible CPU. From userspace's point of view, this looks the same as if the incompatible CPUs have been hotplugged off in the task's affinity mask. Similarly, if a subsequent execve() reverts to a compatible image, then the old affinity is restored if it is still valid.

In preparation for restricting the affinity mask for compat tasks on arm64 systems without uniform support for 32-bit applications, introduce {force,relax}_compatible_cpus_allowed_ptr(), which respectively restrict and restore the affinity mask for a task based on the compatible CPUs.

---
 include/linux/sched.h |   2 +
 kernel/sched/core.c   | 177 ++++++++++++++++++++++++++++++++++++++----
 kernel/sched/sched.h  |   1 +
 3 files changed, 164 insertions(+), 16 deletions(-)
include/linux/sched.h +2 -0
index db32d4f7e5b3..91a6cfeae242 100644
@@ -1691,6 +1691,8 @@ extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new
 extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
 extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
 extern void release_user_cpus_ptr(struct task_struct *p);
+extern void force_compatible_cpus_allowed_ptr(struct task_struct *p);
+extern void relax_compatible_cpus_allowed_ptr(struct task_struct *p);
 #else
 static inline void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
 {
kernel/sched/core.c +161 -16

215 lines not on this page · raw message

kernel/sched/sched.h +1 -0
index a189bec13729..29c35b51411b 100644
@@ -1956,6 +1956,7 @@ extern struct task_struct *pick_next_task_idle(struct rq *rq);
 #define SCA_CHECK		0x01
 #define SCA_MIGRATE_DISABLE	0x02
 #define SCA_MIGRATE_ENABLE	0x04
+#define SCA_USER		0x08
 
 #ifdef CONFIG_SMP
 
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
#30 lore raw

Re: [PATCH v8 11/19] sched: Allow task CPU affinity to be restricted on asymmetric systems

Quoted 32 lines · Will Deacon
kernel/sched/core.c +32 -0
+static int restrict_cpus_allowed_ptr(struct task_struct *p,
+				     struct cpumask *new_mask,
+				     const struct cpumask *subset_mask)
+{
+	struct rq_flags rf;
+	struct rq *rq;
+	int err;
+	struct cpumask *user_mask = NULL;
+
+	if (!p->user_cpus_ptr) {
+		user_mask = kmalloc(cpumask_size(), GFP_KERNEL);
+
+		if (!user_mask)
+			return -ENOMEM;
+	}
+
+	rq = task_rq_lock(p, &rf);
+
+	/*
+	 * Forcefully restricting the affinity of a deadline task is
+	 * likely to cause problems, so fail and noisily override the
+	 * mask entirely.
+	 */
+	if (task_has_dl_policy(p) && dl_bandwidth_enabled()) {
+		err = -EPERM;
+		goto err_unlock;
+	}
+
+	if (!cpumask_and(new_mask, &p->cpus_mask, subset_mask)) {
+		err = -EINVAL;
+		goto err_unlock;
+	}

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2512

Quoted 10 lines
kernel/sched/core.c +10 -0
+
+	/*
+	 * We're about to butcher the task affinity, so keep track of what
+	 * the user asked for in case we're able to restore it later on.
+	 */
+	if (user_mask) {
+		cpumask_copy(user_mask, p->cpus_ptr);
+		p->user_cpus_ptr = user_mask;
+	}
+

Shouldn't that be done before any of the bailouts above, so we can potentially restore the mask even if we end up forcefully expanding the affinity?

Quoted 17 lines
kernel/sched/core.c +17 -0
+	return __set_cpus_allowed_ptr_locked(p, new_mask, 0, rq, &rf);
+
+err_unlock:
+	task_rq_unlock(rq, p, &rf);
+	kfree(user_mask);
+	return err;
+}
+
+/*
+ * Restrict the CPU affinity of task @p so that it is a subset of
+ * task_cpu_possible_mask() and point @p->user_cpu_ptr to a copy of the
+ * old affinity mask. If the resulting mask is empty, we warn and walk
+ * up the cpuset hierarchy until we find a suitable mask.
+ */
+void force_compatible_cpus_allowed_ptr(struct task_struct *p)
+{
+	cpumask_var_t new_mask;

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2539

Quoted 10 lines
kernel/sched/core.c +10 -0
+	const struct cpumask *override_mask = task_cpu_possible_mask(p);
+
+	alloc_cpumask_var(&new_mask, GFP_KERNEL);
+
+	/*
+	 * __migrate_task() can fail silently in the face of concurrent
+	 * offlining of the chosen destination CPU, so take the hotplug
+	 * lock to ensure that the migration succeeds.
+	 */
+	cpus_read_lock();

I'm thinking this might not be required with:

http://lore.kernel.org/r/[email protected]

but then again this isn't merged yet :-)

Quoted 40 lines
kernel/sched/core.c +40 -0
+	if (!cpumask_available(new_mask))
+		goto out_set_mask;
+
+	if (!restrict_cpus_allowed_ptr(p, new_mask, override_mask))
+		goto out_free_mask;
+
+	/*
+	 * We failed to find a valid subset of the affinity mask for the
+	 * task, so override it based on its cpuset hierarchy.
+	 */
+	cpuset_cpus_allowed(p, new_mask);
+	override_mask = new_mask;
+
+out_set_mask:
+	if (printk_ratelimit()) {
+		printk_deferred("Overriding affinity for process %d (%s) to CPUs %*pbl\n",
+				task_pid_nr(p), p->comm,
+				cpumask_pr_args(override_mask));
+	}
+
+	WARN_ON(set_cpus_allowed_ptr(p, override_mask));
+out_free_mask:
+	cpus_read_unlock();
+	free_cpumask_var(new_mask);
+}
+
+static int
+__sched_setaffinity(struct task_struct *p, const struct cpumask *mask);
+
+/*
+ * Restore the affinity of a task @p which was previously restricted by a
+ * call to force_compatible_cpus_allowed_ptr(). This will clear (and free)
+ * @p->user_cpus_ptr.
+ */
+void relax_compatible_cpus_allowed_ptr(struct task_struct *p)
+{
+	unsigned long flags;
+	struct cpumask *mask = p->user_cpus_ptr;
+
+	/*

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2589

Quoted 10 lines
kernel/sched/core.c +10 -0
+	 * Try to restore the old affinity mask. If this fails, then
+	 * we free the mask explicitly to avoid it being inherited across
+	 * a subsequent fork().
+	 */
+	if (!mask || !__sched_setaffinity(p, mask))
+		return;
+
+	raw_spin_lock_irqsave(&p->pi_lock, flags);
+	release_user_cpus_ptr(p);
+	raw_spin_unlock_irqrestore(&p->pi_lock, flags);

AFAICT an affinity change can happen between __sched_setaffinity() and reacquiring the ->pi_lock. Right now this can't be another force_compatible_cpus_allowed_ptr() because this is only driven by arch_setup_new_exec() against current, so we should be fine, but here be dragons.

author maintainer
#31 lore raw

Re: [PATCH v8 11/19] sched: Allow task CPU affinity to be restricted on asymmetric systems

Quoted 39 lines · Valentin Schneider
Quoted 38 lines · Will Deacon
kernel/sched/core.c +38 -0
+static int restrict_cpus_allowed_ptr(struct task_struct *p,
+				     struct cpumask *new_mask,
+				     const struct cpumask *subset_mask)
+{
+	struct rq_flags rf;
+	struct rq *rq;
+	int err;
+	struct cpumask *user_mask = NULL;
+
+	if (!p->user_cpus_ptr) {
+		user_mask = kmalloc(cpumask_size(), GFP_KERNEL);
+
+		if (!user_mask)
+			return -ENOMEM;
+	}
+
+	rq = task_rq_lock(p, &rf);
+
+	/*
+	 * Forcefully restricting the affinity of a deadline task is
+	 * likely to cause problems, so fail and noisily override the
+	 * mask entirely.
+	 */
+	if (task_has_dl_policy(p) && dl_bandwidth_enabled()) {
+		err = -EPERM;
+		goto err_unlock;
+	}
+
+	if (!cpumask_and(new_mask, &p->cpus_mask, subset_mask)) {
+		err = -EINVAL;
+		goto err_unlock;
+	}
+
+	/*
+	 * We're about to butcher the task affinity, so keep track of what
+	 * the user asked for in case we're able to restore it later on.
+	 */
+	if (user_mask) {
Quoted 8 lines

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2512

Quoted 4 lines
kernel/sched/core.c +4 -0
+		cpumask_copy(user_mask, p->cpus_ptr);
+		p->user_cpus_ptr = user_mask;
+	}
+

Shouldn't that be done before any of the bailouts above, so we can potentially restore the mask even if we end up forcefully expanding the affinity?

I don't think so. I deliberately only track the old mask if we've managed to take a subset for the 32-bit task. If we end up having to override the mask entirely, then I treat it the same way as an explicit affinity change (only with a warning printed) and don't then try to restore the old mask -- it feels like we'd be overriding the affinity twice if we tried to do that.

Quoted 30 lines
Quoted 17 lines
kernel/sched/core.c +17 -0
+	return __set_cpus_allowed_ptr_locked(p, new_mask, 0, rq, &rf);
+
+err_unlock:
+	task_rq_unlock(rq, p, &rf);
+	kfree(user_mask);
+	return err;
+}
+
+/*
+ * Restrict the CPU affinity of task @p so that it is a subset of
+ * task_cpu_possible_mask() and point @p->user_cpu_ptr to a copy of the
+ * old affinity mask. If the resulting mask is empty, we warn and walk
+ * up the cpuset hierarchy until we find a suitable mask.
+ */
+void force_compatible_cpus_allowed_ptr(struct task_struct *p)
+{
+	cpumask_var_t new_mask;

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2539

Quoted 10 lines
kernel/sched/core.c +10 -0
+	const struct cpumask *override_mask = task_cpu_possible_mask(p);
+
+	alloc_cpumask_var(&new_mask, GFP_KERNEL);
+
+	/*
+	 * __migrate_task() can fail silently in the face of concurrent
+	 * offlining of the chosen destination CPU, so take the hotplug
+	 * lock to ensure that the migration succeeds.
+	 */
+	cpus_read_lock();

I'm thinking this might not be required with:

Quoted 3 lines

http://lore.kernel.org/r/[email protected]

but then again this isn't merged yet :-)

Agreed, if that patch does what it says on the tin ;)

I need to digest your reply to me, as this is mind-bending stuff.

Quoted 22 lines
Quoted 22 lines
kernel/sched/core.c +22 -0
+static int
+__sched_setaffinity(struct task_struct *p, const struct cpumask *mask);
+
+/*
+ * Restore the affinity of a task @p which was previously restricted by a
+ * call to force_compatible_cpus_allowed_ptr(). This will clear (and free)
+ * @p->user_cpus_ptr.
+ */
+void relax_compatible_cpus_allowed_ptr(struct task_struct *p)
+{
+	unsigned long flags;
+	struct cpumask *mask = p->user_cpus_ptr;
+
+	/*
+	 * Try to restore the old affinity mask. If this fails, then
+	 * we free the mask explicitly to avoid it being inherited across
+	 * a subsequent fork().
+	 */
+	if (!mask || !__sched_setaffinity(p, mask))
+		return;
+
+	raw_spin_lock_irqsave(&p->pi_lock, flags);
Quoted 8 lines

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2589

Quoted 2 lines
kernel/sched/core.c +2 -0
+	release_user_cpus_ptr(p);
+	raw_spin_unlock_irqrestore(&p->pi_lock, flags);

AFAICT an affinity change can happen between __sched_setaffinity() and reacquiring the ->pi_lock. Right now this can't be another force_compatible_cpus_allowed_ptr() because this is only driven by arch_setup_new_exec() against current, so we should be fine, but here be dragons.

That's a good point. I'll add a comment for now, since I'm not sure who else might end up using this in future. Generally it's pretty agnostic to how it's being used, but we're certainly relying on the serialisation of restrict/relax calls.

Will

#32 lore raw

Re: [PATCH v8 11/19] sched: Allow task CPU affinity to be restricted on asymmetric systems

Quoted 31 lines · Will Deacon
Quoted 21 lines · Valentin Schneider
Quoted 20 lines · Will Deacon
kernel/sched/core.c +20 -0
+	/*
+	 * Forcefully restricting the affinity of a deadline task is
+	 * likely to cause problems, so fail and noisily override the
+	 * mask entirely.
+	 */
+	if (task_has_dl_policy(p) && dl_bandwidth_enabled()) {
+		err = -EPERM;
+		goto err_unlock;
+	}
+
+	if (!cpumask_and(new_mask, &p->cpus_mask, subset_mask)) {
+		err = -EINVAL;
+		goto err_unlock;
+	}
+
+	/*
+	 * We're about to butcher the task affinity, so keep track of what
+	 * the user asked for in case we're able to restore it later on.
+	 */
+	if (user_mask) {
Quoted 8 lines

kernel/sched/core.c static int __set_cpus_allowed_ptr(struct task_struct *p, · line 2512

Quoted 4 lines
kernel/sched/core.c +4 -0
+		cpumask_copy(user_mask, p->cpus_ptr);
+		p->user_cpus_ptr = user_mask;
+	}
+

Shouldn't that be done before any of the bailouts above, so we can potentially restore the mask even if we end up forcefully expanding the affinity?

Quoted 6 lines

I don't think so. I deliberately only track the old mask if we've managed to take a subset for the 32-bit task. If we end up having to override the mask entirely, then I treat it the same way as an explicit affinity change (only with a warning printed) and don't then try to restore the old mask -- it feels like we'd be overriding the affinity twice if we tried to do that.

Put in this way, it does make sense to me. Thanks!

author maintainer
#33 lore raw

PATCH v8 12/19 sched: Introduce task_cpus_dl_admissible() to check proposed affinity

In reply to Will Deacon

In preparation for restricting the affinity of a task during execve() on arm64, introduce a new task_cpus_dl_admissible() helper function to give an indication as to whether the restricted mask is admissible for a deadline task.

---
 include/linux/sched.h |  6 ++++++
 kernel/sched/core.c   | 44 +++++++++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 16 deletions(-)
include/linux/sched.h +6 -0
index 91a6cfeae242..9b17d8cfa6ef 100644
@@ -1691,6 +1691,7 @@ extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new
 extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
 extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
 extern void release_user_cpus_ptr(struct task_struct *p);
+extern bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask);
 extern void force_compatible_cpus_allowed_ptr(struct task_struct *p);
 extern void relax_compatible_cpus_allowed_ptr(struct task_struct *p);
 #else
@@ -1713,6 +1714,11 @@ static inline void release_user_cpus_ptr(struct task_struct *p)
 {
 	WARN_ON(p->user_cpus_ptr);
 }
+
+static inline bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask)
+{
+	return true;
+}
 #endif
 
 extern int yield_to(struct task_struct *p, bool preempt);
kernel/sched/core.c +28 -16

57 lines not on this page · raw message

Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 12/19
#34 lore raw

Re: [PATCH v8 12/19] sched: Introduce task_cpus_dl_admissible() to check proposed affinity

Quoted 40 lines · Will Deacon

In preparation for restricting the affinity of a task during execve() on arm64, introduce a new task_cpus_dl_admissible() helper function to give an indication as to whether the restricted mask is admissible for a deadline task.

---
 include/linux/sched.h |  6 ++++++
 kernel/sched/core.c   | 44 +++++++++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 16 deletions(-)
include/linux/sched.h +6 -0
index 91a6cfeae242..9b17d8cfa6ef 100644
@@ -1691,6 +1691,7 @@ extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new
 extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
 extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
 extern void release_user_cpus_ptr(struct task_struct *p);
+extern bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask);
 extern void force_compatible_cpus_allowed_ptr(struct task_struct *p);
 extern void relax_compatible_cpus_allowed_ptr(struct task_struct *p);
 #else
@@ -1713,6 +1714,11 @@ static inline void release_user_cpus_ptr(struct task_struct *p)
 {
 	WARN_ON(p->user_cpus_ptr);
 }
+
+static inline bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask)
+{
+	return true;
+}
 #endif
 
 extern int yield_to(struct task_struct *p, bool preempt);
kernel/sched/core.c +0 -0
index 58e2cf7520c0..b4f8dc18ae11 100644

kernel/sched/core.c SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, · line 6937

Quoted 5 lines
kernel/sched/core.c +2 -0
@@ -6933,6 +6933,31 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr,
 	return retval;
 }
 
+#ifdef CONFIG_SMP
+bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask)

Would you mind renaming it to dl_task_check_affinity(), in the case of a v9? It will look coherent with dl_task_can_attach()...

Thanks!
-- Daniel

author maintainer
#35 lore raw

Re: [PATCH v8 12/19] sched: Introduce task_cpus_dl_admissible() to check proposed affinity

Quoted 44 lines · Daniel Bristot de Oliveira
Quoted 43 lines · Will Deacon

In preparation for restricting the affinity of a task during execve() on arm64, introduce a new task_cpus_dl_admissible() helper function to give an indication as to whether the restricted mask is admissible for a deadline task.

---
 include/linux/sched.h |  6 ++++++
 kernel/sched/core.c   | 44 +++++++++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 16 deletions(-)
include/linux/sched.h +6 -0
index 91a6cfeae242..9b17d8cfa6ef 100644
@@ -1691,6 +1691,7 @@ extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new
 extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
 extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
 extern void release_user_cpus_ptr(struct task_struct *p);
+extern bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask);
 extern void force_compatible_cpus_allowed_ptr(struct task_struct *p);
 extern void relax_compatible_cpus_allowed_ptr(struct task_struct *p);
 #else
@@ -1713,6 +1714,11 @@ static inline void release_user_cpus_ptr(struct task_struct *p)
 {
 	WARN_ON(p->user_cpus_ptr);
 }
+
+static inline bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask)
+{
+	return true;
+}
 #endif
 
 extern int yield_to(struct task_struct *p, bool preempt);
kernel/sched/core.c +0 -0
index 58e2cf7520c0..b4f8dc18ae11 100644
@@ -6933,6 +6933,31 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr,
 	return retval;
 }
 
Quoted 5 lines

kernel/sched/core.c SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, · line 6937

Quoted 2 lines
kernel/sched/core.c +2 -0
+#ifdef CONFIG_SMP
+bool task_cpus_dl_admissible(struct task_struct *p, const struct cpumask *mask)

Would you mind renaming it to dl_task_check_affinity(), in the case of a v9? It will look coherent with dl_task_can_attach()...

Of course! I struggled with the naming myself, and your suggestion is much better.

Quoted 1 line

Cheers!

Will

author maintainer
#36 lore raw

PATCH v8 13/19 arm64: Implement task_cpu_possible_mask()

In reply to Will Deacon

Provide an implementation of task_cpu_possible_mask() so that we can prevent 64-bit-only cores being added to the 'cpus_mask' for compat tasks on systems with mismatched 32-bit support at EL0,

---
 arch/arm64/include/asm/mmu_context.h | 13 +++++++++++++
 1 file changed, 13 insertions(+)
arch/arm64/include/asm/mmu_context.h +13 -0
index d3cef9133539..bb9b7510f334 100644
@@ -231,6 +231,19 @@ switch_mm(struct mm_struct *prev, struct mm_struct *next,
 	update_saved_ttbr0(tsk, next);
 }
 
+static inline const struct cpumask *
+task_cpu_possible_mask(struct task_struct *p)
+{
+	if (!static_branch_unlikely(&arm64_mismatched_32bit_el0))
+		return cpu_possible_mask;
+
+	if (!is_compat_thread(task_thread_info(p)))
+		return cpu_possible_mask;
+
+	return system_32bit_el0_cpumask();
+}
+#define task_cpu_possible_mask	task_cpu_possible_mask
+
 void verify_cpu_asid_bits(void);
 void post_ttbr_update_workaround(void);
 
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#37 lore raw

PATCH v8 14/19 arm64: exec: Adjust affinity for compat tasks with mismatched 32-bit EL0

In reply to Will Deacon

When exec'ing a 32-bit task on a system with mismatched support for 32-bit EL0, try to ensure that it starts life on a CPU that can actually run it.

Similarly, when exec'ing a 64-bit task on such a system, try to restore the old affinity mask if it was previously restricted.

---
 arch/arm64/include/asm/elf.h |  6 ++----
 arch/arm64/kernel/process.c  | 39 +++++++++++++++++++++++++++++++++++-
 2 files changed, 40 insertions(+), 5 deletions(-)
arch/arm64/include/asm/elf.h +2 -4
index 8d1c8dcb87fd..97932fbf973d 100644
@@ -213,10 +213,8 @@ typedef compat_elf_greg_t		compat_elf_gregset_t[COMPAT_ELF_NGREG];
 
 /* AArch32 EABI. */
 #define EF_ARM_EABI_MASK		0xff000000
-#define compat_elf_check_arch(x)	(system_supports_32bit_el0() && \
-					 ((x)->e_machine == EM_ARM) && \
-					 ((x)->e_flags & EF_ARM_EABI_MASK))
-
+int compat_elf_check_arch(const struct elf32_hdr *);
+#define compat_elf_check_arch		compat_elf_check_arch
 #define compat_start_thread		compat_start_thread
 /*
  * Unlike the native SET_PERSONALITY macro, the compat version maintains
arch/arm64/kernel/process.c +38 -1
index f4a91bf1ce0c..3aea06fdd1f9 100644
@@ -22,6 +22,7 @@
 #include <linux/mman.h>
 #include <linux/mm.h>
 #include <linux/nospec.h>
+#include <linux/sched.h>
 #include <linux/stddef.h>
 #include <linux/sysctl.h>
 #include <linux/unistd.h>
@@ -638,6 +639,28 @@ unsigned long arch_align_stack(unsigned long sp)
 	return sp & ~0xf;
 }
 
+#ifdef CONFIG_COMPAT
+int compat_elf_check_arch(const struct elf32_hdr *hdr)
+{
+	if (!system_supports_32bit_el0())
+		return false;
+
+	if ((hdr)->e_machine != EM_ARM)
+		return false;
+
+	if (!((hdr)->e_flags & EF_ARM_EABI_MASK))
+		return false;
+
+	/*
+	 * Prevent execve() of a 32-bit program from a deadline task
+	 * if the restricted affinity mask would be inadmissible on an
+	 * asymmetric system.
+	 */
+	return !static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       task_cpus_dl_admissible(current, system_32bit_el0_cpumask());
+}
+#endif
+
 /*
  * Called from setup_new_exec() after (COMPAT_)SET_PERSONALITY.
  */
@@ -647,8 +670,22 @@ void arch_setup_new_exec(void)
 
 	if (is_compat_task()) {
 		mmflags = MMCF_AARCH32;
-		if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
+
+		/*
+		 * Restrict the CPU affinity mask for a 32-bit task so that
+		 * it contains only 32-bit-capable CPUs.
+		 *
+		 * From the perspective of the task, this looks similar to
+		 * what would happen if the 64-bit-only CPUs were hot-unplugged
+		 * at the point of execve(), although we try a bit harder to
+		 * honour the cpuset hierarchy.
+		 */
+		if (static_branch_unlikely(&arm64_mismatched_32bit_el0)) {
+			force_compatible_cpus_allowed_ptr(current);
 			set_tsk_thread_flag(current, TIF_NOTIFY_RESUME);
+		}
+	} else if (static_branch_unlikely(&arm64_mismatched_32bit_el0)) {
+		relax_compatible_cpus_allowed_ptr(current);
 	}
 
 	current->mm->context.flags = mmflags;
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
Reviewed-by on v8 14/19
#38 lore raw

Re: [PATCH v8 14/19] arm64: exec: Adjust affinity for compat tasks with mismatched 32-bit EL0

Quoted 10 lines · Will Deacon

When exec'ing a 32-bit task on a system with mismatched support for 32-bit EL0, try to ensure that it starts life on a CPU that can actually run it.

Similarly, when exec'ing a 64-bit task on such a system, try to restore the old affinity mask if it was previously restricted.

---

[...]

Quoted 12 lines
arch/arm64/kernel/process.c +11 -0
+#ifdef CONFIG_COMPAT
+int compat_elf_check_arch(const struct elf32_hdr *hdr)
+{
+	if (!system_supports_32bit_el0())
+		return false;
+
+	if ((hdr)->e_machine != EM_ARM)
+		return false;
+
+	if (!((hdr)->e_flags & EF_ARM_EABI_MASK))
+		return false;

arch/arm64/kernel/process.c unsigned long arch_align_stack(unsigned long sp) · line 662

Quoted 10 lines
arch/arm64/kernel/process.c +10 -0
+
+	/*
+	 * Prevent execve() of a 32-bit program from a deadline task
+	 * if the restricted affinity mask would be inadmissible on an
+	 * asymmetric system.
+	 */
+	return !static_branch_unlikely(&arm64_mismatched_32bit_el0) ||
+	       task_cpus_dl_admissible(current, system_32bit_el0_cpumask());
+}
+#endif

From the DL perspective:

Thanks!
-- Daniel

author maintainer
#39 lore raw

PATCH v8 15/19 arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched system

In reply to Will Deacon

If we want to support 32-bit applications, then when we identify a CPU with mismatched 32-bit EL0 support we must ensure that we will always have an active 32-bit CPU available to us from then on. This is important for the scheduler, because is_cpu_allowed() will be constrained to 32-bit CPUs for compat tasks and forced migration due to a hotplug event will hang if no 32-bit CPUs are available.

On detecting a mismatch, prevent offlining of either the mismatching CPU if it is 32-bit capable, or find the first active 32-bit capable CPU otherwise.

---
 arch/arm64/kernel/cpufeature.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
arch/arm64/kernel/cpufeature.c +19 -1
index 4194a47de62d..b31d7a1eaed6 100644
@@ -2877,15 +2877,33 @@ void __init setup_cpu_features(void)
 
 static int enable_mismatched_32bit_el0(unsigned int cpu)
 {
+	static int lucky_winner = -1;
+
 	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
 	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
 
 	if (cpu_32bit) {
 		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
 		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
-		setup_elf_hwcaps(compat_elf_hwcaps);
 	}
 
+	if (cpumask_test_cpu(0, cpu_32bit_el0_mask) == cpu_32bit)
+		return 0;
+
+	if (lucky_winner >= 0)
+		return 0;
+
+	/*
+	 * We've detected a mismatch. We need to keep one of our CPUs with
+	 * 32-bit EL0 online so that is_cpu_allowed() doesn't end up rejecting
+	 * every CPU in the system for a 32-bit task.
+	 */
+	lucky_winner = cpu_32bit ? cpu : cpumask_any_and(cpu_32bit_el0_mask,
+							 cpu_active_mask);
+	get_cpu_device(lucky_winner)->offline_disabled = true;
+	setup_elf_hwcaps(compat_elf_hwcaps);
+	pr_info("Asymmetric 32-bit EL0 support detected on CPU %u; CPU hot-unplug disabled on CPU %u\n",
+		cpu, lucky_winner);
 	return 0;
 }
 
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
reviewer
#40 lore raw

Re: [PATCH v8 15/19] arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched system

Quoted 22 lines · Will Deacon

If we want to support 32-bit applications, then when we identify a CPU with mismatched 32-bit EL0 support we must ensure that we will always have an active 32-bit CPU available to us from then on. This is important for the scheduler, because is_cpu_allowed() will be constrained to 32-bit CPUs for compat tasks and forced migration due to a hotplug event will hang if no 32-bit CPUs are available.

On detecting a mismatch, prevent offlining of either the mismatching CPU if it is 32-bit capable, or find the first active 32-bit capable CPU otherwise.

---
 arch/arm64/kernel/cpufeature.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
arch/arm64/kernel/cpufeature.c +0 -0
index 4194a47de62d..b31d7a1eaed6 100644

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2880

Quoted 4 lines
arch/arm64/kernel/cpufeature.c +1 -0
@@ -2877,15 +2877,33 @@ void __init setup_cpu_features(void)
 
 static int enable_mismatched_32bit_el0(unsigned int cpu)
 {
+	static int lucky_winner = -1;

This is cute, but could we please give it a meaningful name, e.g. pinned_cpu ?

Quoted 19 lines
arch/arm64/kernel/cpufeature.c +10 -1
+
 	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
 	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
 
 	if (cpu_32bit) {
 		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
 		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
-		setup_elf_hwcaps(compat_elf_hwcaps);
 	}
 
+	if (cpumask_test_cpu(0, cpu_32bit_el0_mask) == cpu_32bit)
+		return 0;
+
+	if (lucky_winner >= 0)
+		return 0;
+
+	/*
+	 * We've detected a mismatch. We need to keep one of our CPUs with
+	 * 32-bit EL0 online so that is_cpu_allowed() doesn't end up rejecting

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2908

Quoted 10 lines
arch/arm64/kernel/cpufeature.c +8 -0
+	 * every CPU in the system for a 32-bit task.
+	 */
+	lucky_winner = cpu_32bit ? cpu : cpumask_any_and(cpu_32bit_el0_mask,
+							 cpu_active_mask);
+	get_cpu_device(lucky_winner)->offline_disabled = true;
+	setup_elf_hwcaps(compat_elf_hwcaps);
+	pr_info("Asymmetric 32-bit EL0 support detected on CPU %u; CPU hot-unplug disabled on CPU %u\n",
+		cpu, lucky_winner);
 	return 0;
 }

I guess this is going to play havoc with kexec and hibernate. :/

Thanks,
Mark.

author maintainer
#41 lore raw

Re: [PATCH v8 15/19] arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched system

Quoted 24 lines · Mark Rutland
Quoted 23 lines · Will Deacon

If we want to support 32-bit applications, then when we identify a CPU with mismatched 32-bit EL0 support we must ensure that we will always have an active 32-bit CPU available to us from then on. This is important for the scheduler, because is_cpu_allowed() will be constrained to 32-bit CPUs for compat tasks and forced migration due to a hotplug event will hang if no 32-bit CPUs are available.

On detecting a mismatch, prevent offlining of either the mismatching CPU if it is 32-bit capable, or find the first active 32-bit capable CPU otherwise.

---
 arch/arm64/kernel/cpufeature.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
arch/arm64/kernel/cpufeature.c +0 -0
index 4194a47de62d..b31d7a1eaed6 100644
@@ -2877,15 +2877,33 @@ void __init setup_cpu_features(void)
 
Quoted 6 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2880

Quoted 3 lines
arch/arm64/kernel/cpufeature.c +1 -0
 static int enable_mismatched_32bit_el0(unsigned int cpu)
 {
+	static int lucky_winner = -1;

This is cute, but could we please give it a meaningful name, e.g. pinned_cpu ?

I really don't see the problem, nor why it's "cute".

Tell you what, I'll add a comment instead:

/*
* The first 32-bit-capable CPU we detected and so can no longer
* be offlined by userspace. -1 indicates we haven't yet onlined
* a 32-bit-capable CPU.
*/

Quoted 22 lines
Quoted 22 lines
arch/arm64/kernel/cpufeature.c +13 -1
 	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
 	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
 
 	if (cpu_32bit) {
 		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
 		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
-		setup_elf_hwcaps(compat_elf_hwcaps);
 	}
 
+	if (cpumask_test_cpu(0, cpu_32bit_el0_mask) == cpu_32bit)
+		return 0;
+
+	if (lucky_winner >= 0)
+		return 0;
+
+	/*
+	 * We've detected a mismatch. We need to keep one of our CPUs with
+	 * 32-bit EL0 online so that is_cpu_allowed() doesn't end up rejecting
+	 * every CPU in the system for a 32-bit task.
+	 */
+	lucky_winner = cpu_32bit ? cpu : cpumask_any_and(cpu_32bit_el0_mask,
+							 cpu_active_mask);
Quoted 8 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2908

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +4 -0
+	get_cpu_device(lucky_winner)->offline_disabled = true;
+	setup_elf_hwcaps(compat_elf_hwcaps);
+	pr_info("Asymmetric 32-bit EL0 support detected on CPU %u; CPU hot-unplug disabled on CPU %u\n",
+		cpu, lucky_winner);
 	return 0;
 }

I guess this is going to play havoc with kexec and hibernate. :/

The kernel can still offline the CPUs (see the whole freezer mess that I linked to in the cover letter). What specific havoc are you thinking of?

Will

reviewer
#42 lore raw

Re: [PATCH v8 15/19] arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched system

Quoted 34 lines · Will Deacon
Quoted 24 lines · Mark Rutland
Quoted 23 lines · Will Deacon

If we want to support 32-bit applications, then when we identify a CPU with mismatched 32-bit EL0 support we must ensure that we will always have an active 32-bit CPU available to us from then on. This is important for the scheduler, because is_cpu_allowed() will be constrained to 32-bit CPUs for compat tasks and forced migration due to a hotplug event will hang if no 32-bit CPUs are available.

On detecting a mismatch, prevent offlining of either the mismatching CPU if it is 32-bit capable, or find the first active 32-bit capable CPU otherwise.

---
 arch/arm64/kernel/cpufeature.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
arch/arm64/kernel/cpufeature.c +0 -0
index 4194a47de62d..b31d7a1eaed6 100644
@@ -2877,15 +2877,33 @@ void __init setup_cpu_features(void)
 
Quoted 6 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2880

Quoted 3 lines
arch/arm64/kernel/cpufeature.c +1 -0
 static int enable_mismatched_32bit_el0(unsigned int cpu)
 {
+	static int lucky_winner = -1;

This is cute, but could we please give it a meaningful name, e.g. pinned_cpu ?

I really don't see the problem, nor why it's "cute".

Quoted 7 lines

Tell you what, I'll add a comment instead:

/*
* The first 32-bit-capable CPU we detected and so can no longer
* be offlined by userspace. -1 indicates we haven't yet onlined
* a 32-bit-capable CPU.
*/

Thanks for the comment; that's helpful.

However, my concern here is that when we inevitably have to discuss this with others in future, "lucky winner" is jarring (and also unclear to those where English is not their native language). For clarity, it would be really nice to use a term like "cpu", "chosen_cpu", "pinned_cpu", etc.

However, you're the maintainer; choose what you think is appropriate.

Quoted 31 lines
Quoted 22 lines
Quoted 22 lines
arch/arm64/kernel/cpufeature.c +13 -1
 	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
 	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
 
 	if (cpu_32bit) {
 		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
 		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
-		setup_elf_hwcaps(compat_elf_hwcaps);
 	}
 
+	if (cpumask_test_cpu(0, cpu_32bit_el0_mask) == cpu_32bit)
+		return 0;
+
+	if (lucky_winner >= 0)
+		return 0;
+
+	/*
+	 * We've detected a mismatch. We need to keep one of our CPUs with
+	 * 32-bit EL0 online so that is_cpu_allowed() doesn't end up rejecting
+	 * every CPU in the system for a 32-bit task.
+	 */
+	lucky_winner = cpu_32bit ? cpu : cpumask_any_and(cpu_32bit_el0_mask,
+							 cpu_active_mask);
Quoted 8 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2908

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +4 -0
+	get_cpu_device(lucky_winner)->offline_disabled = true;
+	setup_elf_hwcaps(compat_elf_hwcaps);
+	pr_info("Asymmetric 32-bit EL0 support detected on CPU %u; CPU hot-unplug disabled on CPU %u\n",
+		cpu, lucky_winner);
 	return 0;
 }

I guess this is going to play havoc with kexec and hibernate. :/

Quoted 2 lines

The kernel can still offline the CPUs (see the whole freezer mess that I linked to in the cover letter). What specific havoc are you thinking of?

Ah. If this is just inhibiting userspace-driven offlining, that sounds fine.

For kexec, I was concerned that either this would inhibit kexec, or smp_shutdown_nonboot_cpus() would fail to offline the pinned CPU, and that'd trigger a BUG(), which would be unfortunate.

For hibernate, the equivalent is freeze_secondary_cpus(), which I guess is dealt with by the freezer bits you mention.

Thanks,
Mark.

#43 lore raw

Re: [PATCH v8 15/19] arm64: Prevent offlining first CPU with 32-bit EL0 on mismatched system

Quoted 90 lines · Mark Rutland
Quoted 34 lines · Will Deacon
Quoted 24 lines · Mark Rutland
Quoted 23 lines · Will Deacon

If we want to support 32-bit applications, then when we identify a CPU with mismatched 32-bit EL0 support we must ensure that we will always have an active 32-bit CPU available to us from then on. This is important for the scheduler, because is_cpu_allowed() will be constrained to 32-bit CPUs for compat tasks and forced migration due to a hotplug event will hang if no 32-bit CPUs are available.

On detecting a mismatch, prevent offlining of either the mismatching CPU if it is 32-bit capable, or find the first active 32-bit capable CPU otherwise.

---
 arch/arm64/kernel/cpufeature.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
arch/arm64/kernel/cpufeature.c +0 -0
index 4194a47de62d..b31d7a1eaed6 100644
@@ -2877,15 +2877,33 @@ void __init setup_cpu_features(void)
 
Quoted 6 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2880

Quoted 3 lines
arch/arm64/kernel/cpufeature.c +1 -0
 static int enable_mismatched_32bit_el0(unsigned int cpu)
 {
+	static int lucky_winner = -1;

This is cute, but could we please give it a meaningful name, e.g. pinned_cpu ?

I really don't see the problem, nor why it's "cute".

Quoted 7 lines

Tell you what, I'll add a comment instead:

/*
* The first 32-bit-capable CPU we detected and so can no longer
* be offlined by userspace. -1 indicates we haven't yet onlined
* a 32-bit-capable CPU.
*/

Thanks for the comment; that's helpful.

However, my concern here is that when we inevitably have to discuss this with others in future, "lucky winner" is jarring (and also unclear to those where English is not their native language). For clarity, it would be really nice to use a term like "cpu", "chosen_cpu", "pinned_cpu", etc.

However, you're the maintainer; choose what you think is appropriate.

Quoted 31 lines
Quoted 22 lines
Quoted 22 lines
arch/arm64/kernel/cpufeature.c +13 -1
 	struct cpuinfo_arm64 *info = &per_cpu(cpu_data, cpu);
 	bool cpu_32bit = id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0);
 
 	if (cpu_32bit) {
 		cpumask_set_cpu(cpu, cpu_32bit_el0_mask);
 		static_branch_enable_cpuslocked(&arm64_mismatched_32bit_el0);
-		setup_elf_hwcaps(compat_elf_hwcaps);
 	}
 
+	if (cpumask_test_cpu(0, cpu_32bit_el0_mask) == cpu_32bit)
+		return 0;
+
+	if (lucky_winner >= 0)
+		return 0;
+
+	/*
+	 * We've detected a mismatch. We need to keep one of our CPUs with
+	 * 32-bit EL0 online so that is_cpu_allowed() doesn't end up rejecting
+	 * every CPU in the system for a 32-bit task.
+	 */
+	lucky_winner = cpu_32bit ? cpu : cpumask_any_and(cpu_32bit_el0_mask,
+							 cpu_active_mask);
Quoted 8 lines

arch/arm64/kernel/cpufeature.c void __init setup_cpu_features(void) · line 2908

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +4 -0
+	get_cpu_device(lucky_winner)->offline_disabled = true;
+	setup_elf_hwcaps(compat_elf_hwcaps);
+	pr_info("Asymmetric 32-bit EL0 support detected on CPU %u; CPU hot-unplug disabled on CPU %u\n",
+		cpu, lucky_winner);
 	return 0;
 }

I guess this is going to play havoc with kexec and hibernate. :/

Quoted 2 lines

The kernel can still offline the CPUs (see the whole freezer mess that I linked to in the cover letter). What specific havoc are you thinking of?

Ah. If this is just inhibiting userspace-driven offlining, that sounds fine.

Quoted 6 lines

For kexec, I was concerned that either this would inhibit kexec, or smp_shutdown_nonboot_cpus() would fail to offline the pinned CPU, and that'd trigger a BUG(), which would be unfortunate.

For hibernate, the equivalent is freeze_secondary_cpus(), which I guess is dealt with by the freezer bits you mention.

()->offline_disabled will only block offline requests performed by device_offline(). kexec, hibernate, suspend/resume use cpu_online/offline() directly so won't be impacted by that. I have sent patches that make cpu_online/offline() 'private' and not used or exported outside of cpu subsystem and the odd support function for arch code. All other users use device_offline() or add/remove_cpu() now.

I have made sure to test kexec, suspend to disk and ram in my older similar implementation in the past. So we should be good.

Cheers

--
Qais Yousef

author maintainer
#44 lore raw

PATCH v8 16/19 arm64: Advertise CPUs capable of running 32-bit applications in sysfs

In reply to Will Deacon

Since 32-bit applications will be killed if they are caught trying to execute on a 64-bit-only CPU in a mismatched system, advertise the set of 32-bit capable CPUs to userspace in sysfs.

---
 .../ABI/testing/sysfs-devices-system-cpu      |  9 +++++++++
 arch/arm64/kernel/cpufeature.c                | 19 +++++++++++++++++++
 2 files changed, 28 insertions(+)
Documentation/ABI/testing/sysfs-devices-system-cpu +9 -0
index fe13baa53c59..899377b2715a 100644
@@ -494,6 +494,15 @@ Description:	AArch64 CPU registers
 		'identification' directory exposes the CPU ID registers for
 		identifying model and revision of the CPU.
 
+What:		/sys/devices/system/cpu/aarch32_el0
+Date:		May 2021
+Contact:	Linux ARM Kernel Mailing list <[email protected]>
+Description:	Identifies the subset of CPUs in the system that can execute
+		AArch32 (32-bit ARM) applications. If present, the same format as
+		/sys/devices/system/cpu/{offline,online,possible,present} is used.
+		If absent, then all or none of the CPUs can execute AArch32
+		applications and execve() will behave accordingly.
+
 What:		/sys/devices/system/cpu/cpu#/cpu_capacity
 Date:		December 2016
 Contact:	Linux kernel mailing list <[email protected]>
arch/arm64/kernel/cpufeature.c +19 -0
index b31d7a1eaed6..72efdc611b14 100644
@@ -67,6 +67,7 @@
 #include <linux/crash_dump.h>
 #include <linux/sort.h>
 #include <linux/stop_machine.h>
+#include <linux/sysfs.h>
 #include <linux/types.h>
 #include <linux/minmax.h>
 #include <linux/mm.h>
@@ -1297,6 +1298,24 @@ const struct cpumask *system_32bit_el0_cpumask(void)
 	return cpu_possible_mask;
 }
 
+static ssize_t aarch32_el0_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	const struct cpumask *mask = system_32bit_el0_cpumask();
+
+	return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(mask));
+}
+static const DEVICE_ATTR_RO(aarch32_el0);
+
+static int __init aarch32_el0_sysfs_init(void)
+{
+	if (!allow_mismatched_32bit_el0)
+		return 0;
+
+	return device_create_file(cpu_subsys.dev_root, &dev_attr_aarch32_el0);
+}
+device_initcall(aarch32_el0_sysfs_init);
+
 static bool has_32bit_el0(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	if (!has_cpuid_feature(entry, scope))
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#45 lore raw

PATCH v8 17/19 arm64: Hook up cmdline parameter to allow mismatched 32-bit EL0

In reply to Will Deacon

Allow systems with mismatched 32-bit support at EL0 to run 32-bit applications based on a new kernel parameter.

---
 Documentation/admin-guide/kernel-parameters.txt | 8 ++++++++
 arch/arm64/kernel/cpufeature.c                  | 7 +++++++
 2 files changed, 15 insertions(+)
Documentation/admin-guide/kernel-parameters.txt +8 -0
index cb89dbdedc46..a2e453919bb6 100644
@@ -287,6 +287,14 @@
 			do not want to use tracing_snapshot_alloc() as it needs
 			to be done where GFP_KERNEL allocations are allowed.
 
+	allow_mismatched_32bit_el0 [ARM64]
+			Allow execve() of 32-bit applications and setting of the
+			PER_LINUX32 personality on systems where only a strict
+			subset of the CPUs support 32-bit EL0. When this
+			parameter is present, the set of CPUs supporting 32-bit
+			EL0 is indicated by /sys/devices/system/cpu/aarch32_el0
+			and hot-unplug operations may be restricted.
+
 	amd_iommu=	[HW,X86-64]
 			Pass parameters to the AMD IOMMU driver in the system.
 			Possible values are:
arch/arm64/kernel/cpufeature.c +7 -0
index 72efdc611b14..f2c97baa050f 100644
@@ -1298,6 +1298,13 @@ const struct cpumask *system_32bit_el0_cpumask(void)
 	return cpu_possible_mask;
 }
 
+static int __init parse_32bit_el0_param(char *str)
+{
+	allow_mismatched_32bit_el0 = true;
+	return 0;
+}
+early_param("allow_mismatched_32bit_el0", parse_32bit_el0_param);
+
 static ssize_t aarch32_el0_show(struct device *dev,
 				struct device_attribute *attr, char *buf)
 {
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#46 lore raw

PATCH v8 18/19 arm64: Remove logic to kill 32-bit tasks on 64-bit-only cores

In reply to Will Deacon

The scheduler now knows enough about these braindead systems to place 32-bit tasks accordingly, so throw out the safety checks and allow the ret-to-user path to avoid do_notify_resume() if there is nothing to do.

---
 arch/arm64/kernel/process.c | 14 +-------------
 arch/arm64/kernel/signal.c  | 26 --------------------------
 2 files changed, 1 insertion(+), 39 deletions(-)
arch/arm64/kernel/process.c +1 -13
index 3aea06fdd1f9..5581c376644e 100644
@@ -528,15 +528,6 @@ static void erratum_1418040_thread_switch(struct task_struct *prev,
 	write_sysreg(val, cntkctl_el1);
 }
 
-static void compat_thread_switch(struct task_struct *next)
-{
-	if (!is_compat_thread(task_thread_info(next)))
-		return;
-
-	if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
-		set_tsk_thread_flag(next, TIF_NOTIFY_RESUME);
-}
-
 static void update_sctlr_el1(u64 sctlr)
 {
 	/*
@@ -578,7 +569,6 @@ __notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
 	ssbs_thread_switch(next);
 	erratum_1418040_thread_switch(prev, next);
 	ptrauth_thread_switch_user(next);
-	compat_thread_switch(next);
 
 	/*
 	 * Complete any pending TLB or cache maintenance on this CPU in case
@@ -680,10 +670,8 @@ void arch_setup_new_exec(void)
 		 * at the point of execve(), although we try a bit harder to
 		 * honour the cpuset hierarchy.
 		 */
-		if (static_branch_unlikely(&arm64_mismatched_32bit_el0)) {
+		if (static_branch_unlikely(&arm64_mismatched_32bit_el0))
 			force_compatible_cpus_allowed_ptr(current);
-			set_tsk_thread_flag(current, TIF_NOTIFY_RESUME);
-		}
 	} else if (static_branch_unlikely(&arm64_mismatched_32bit_el0)) {
 		relax_compatible_cpus_allowed_ptr(current);
 	}
arch/arm64/kernel/signal.c +0 -26
index f8192f4ae0b8..6237486ff6bb 100644
@@ -911,19 +911,6 @@ static void do_signal(struct pt_regs *regs)
 	restore_saved_sigmask();
 }
 
-static bool cpu_affinity_invalid(struct pt_regs *regs)
-{
-	if (!compat_user_mode(regs))
-		return false;
-
-	/*
-	 * We're preemptible, but a reschedule will cause us to check the
-	 * affinity again.
-	 */
-	return !cpumask_test_cpu(raw_smp_processor_id(),
-				 system_32bit_el0_cpumask());
-}
-
 asmlinkage void do_notify_resume(struct pt_regs *regs,
 				 unsigned long thread_flags)
 {
@@ -951,19 +938,6 @@ asmlinkage void do_notify_resume(struct pt_regs *regs,
 			if (thread_flags & _TIF_NOTIFY_RESUME) {
 				tracehook_notify_resume(regs);
 				rseq_handle_notify_resume(NULL, regs);
-
-				/*
-				 * If we reschedule after checking the affinity
-				 * then we must ensure that TIF_NOTIFY_RESUME
-				 * is set so that we check the affinity again.
-				 * Since tracehook_notify_resume() clears the
-				 * flag, ensure that the compiler doesn't move
-				 * it after the affinity check.
-				 */
-				barrier();
-
-				if (cpu_affinity_invalid(regs))
-					force_sig(SIGKILL);
 			}
 
 			if (thread_flags & _TIF_FOREIGN_FPSTATE)
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog
author maintainer
#47 lore raw

PATCH v8 19/19 Documentation: arm64: describe asymmetric 32-bit support

In reply to Will Deacon

Document support for running 32-bit tasks on asymmetric 32-bit systems and its impact on the user ABI when enabled.

---
 .../admin-guide/kernel-parameters.txt         |   3 +
 Documentation/arm64/asymmetric-32bit.rst      | 155 ++++++++++++++++++
 Documentation/arm64/index.rst                 |   1 +
 3 files changed, 159 insertions(+)
 create mode 100644 Documentation/arm64/asymmetric-32bit.rst
Documentation/admin-guide/kernel-parameters.txt +3 -0
index a2e453919bb6..5a1dc7e628a5 100644
@@ -295,6 +295,9 @@
 			EL0 is indicated by /sys/devices/system/cpu/aarch32_el0
 			and hot-unplug operations may be restricted.
 
+			See Documentation/arm64/asymmetric-32bit.rst for more
+			information.
+
 	amd_iommu=	[HW,X86-64]
 			Pass parameters to the AMD IOMMU driver in the system.
 			Possible values are:
Documentation/arm64/asymmetric-32bit.rst new file +155 -0

155 lines not on this page · raw message

Documentation/arm64/index.rst +1 -0
index 97d65ba12a35..4f840bac083e 100644
@@ -10,6 +10,7 @@ ARM64 Architecture
     acpi_object_usage
     amu
     arm-acpi
+    asymmetric-32bit
     booting
     cpu-feature-registers
     elf_hwcaps
Signature · 1 line
2.32.0.rc0.204.g9fa02ecfa5-goog