[PATCH v7 11/33] arm64: idreg-override: Use relative references to override variables

Ard Biesheuvel ardb at kernel.org
Fri Nov 11 09:11:39 PST 2022


To prepare the idreg-override for running in a context where statically
initialized absolute symbol references are not permitted, use place
relative relocations to refer to the 'override' global variables in each
feature override descriptor set, and populate the regs[] array using
relative references as well.

Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
---
 arch/arm64/kernel/idreg-override.c | 144 +++++++++-----------
 1 file changed, 63 insertions(+), 81 deletions(-)

diff --git a/arch/arm64/kernel/idreg-override.c b/arch/arm64/kernel/idreg-override.c
index d7fc813ba5913e27..f8ae7f6d0d9b4fd0 100644
--- a/arch/arm64/kernel/idreg-override.c
+++ b/arch/arm64/kernel/idreg-override.c
@@ -6,6 +6,7 @@
  * Author: Marc Zyngier <maz at kernel.org>
  */
 
+#include <linux/build_bug.h>
 #include <linux/ctype.h>
 #include <linux/kernel.h>
 #include <linux/libfdt.h>
@@ -22,18 +23,29 @@
 static u64 __boot_status __initdata;
 
 struct ftr_set_desc {
-	char 				name[FTR_DESC_NAME_LEN];
-	struct arm64_ftr_override	*override;
+	s32		override_offset; 	// must remain first
+	char 		name[FTR_DESC_NAME_LEN];
 	struct {
-		char			name[FTR_DESC_FIELD_LEN];
-		u8			shift;
-		u8			width;
-		bool			(*filter)(u64 val);
-	} 				fields[];
+		char	name[FTR_DESC_FIELD_LEN];
+		u8	shift;
+		u8	width;
+		bool	(*filter)(u64 val);
+	} 		fields[];
 };
 
+static_assert(offsetof(struct ftr_set_desc, override_offset) == 0);
+
 #define FIELD(n, s, f)	{ .name = n, .shift = s, .width = 4, .filter = f }
 
+#define DEFINE_OVERRIDE(__idx, __id, __name, __ovr, ...)		\
+	asmlinkage const struct ftr_set_desc __initconst __id = {	\
+		.name	= __name,					\
+		.fields = { __VA_ARGS__ },				\
+	};								\
+	asm(".globl " #__ovr "; "					\
+	    ".reloc " #__id ", R_AARCH64_PREL32, " #__ovr "; "		\
+	    ".reloc regs + (4 * " #__idx "), R_AARCH64_PREL32, " #__id)
+
 static bool __init mmfr1_vh_filter(u64 val)
 {
 	/*
@@ -46,14 +58,9 @@ static bool __init mmfr1_vh_filter(u64 val)
 		 val == 0);
 }
 
-static const struct ftr_set_desc mmfr1 __initconst = {
-	.name		= "id_aa64mmfr1",
-	.override	= &id_aa64mmfr1_override,
-	.fields		= {
+DEFINE_OVERRIDE(0, mmfr1, "id_aa64mmfr1", id_aa64mmfr1_override,
 		FIELD("vh", ID_AA64MMFR1_EL1_VH_SHIFT, mmfr1_vh_filter),
-		{}
-	},
-};
+		{});
 
 static bool __init pfr0_sve_filter(u64 val)
 {
@@ -70,14 +77,9 @@ static bool __init pfr0_sve_filter(u64 val)
 	return true;
 }
 
-static const struct ftr_set_desc pfr0 __initconst = {
-	.name		= "id_aa64pfr0",
-	.override	= &id_aa64pfr0_override,
-	.fields		= {
+DEFINE_OVERRIDE(1, pfr0, "id_aa64pfr0", id_aa64pfr0_override,
 	        FIELD("sve", ID_AA64PFR0_EL1_SVE_SHIFT, pfr0_sve_filter),
-		{}
-	},
-};
+		{});
 
 static bool __init pfr1_sme_filter(u64 val)
 {
@@ -94,67 +96,46 @@ static bool __init pfr1_sme_filter(u64 val)
 	return true;
 }
 
-static const struct ftr_set_desc pfr1 __initconst = {
-	.name		= "id_aa64pfr1",
-	.override	= &id_aa64pfr1_override,
-	.fields		= {
+DEFINE_OVERRIDE(2, pfr1, "id_aa64pfr1", id_aa64pfr1_override,
 		FIELD("bt", ID_AA64PFR1_EL1_BT_SHIFT, NULL ),
 		FIELD("mte", ID_AA64PFR1_EL1_MTE_SHIFT, NULL),
 		FIELD("sme", ID_AA64PFR1_EL1_SME_SHIFT, pfr1_sme_filter),
-		{}
-	},
-};
+		{});
 
-static const struct ftr_set_desc isar1 __initconst = {
-	.name		= "id_aa64isar1",
-	.override	= &id_aa64isar1_override,
-	.fields		= {
+DEFINE_OVERRIDE(3, isar1, "id_aa64isar1", id_aa64isar1_override,
 		FIELD("gpi", ID_AA64ISAR1_EL1_GPI_SHIFT, NULL),
 		FIELD("gpa", ID_AA64ISAR1_EL1_GPA_SHIFT, NULL),
 		FIELD("api", ID_AA64ISAR1_EL1_API_SHIFT, NULL),
 		FIELD("apa", ID_AA64ISAR1_EL1_APA_SHIFT, NULL),
-		{}
-	},
-};
+		{});
 
-static const struct ftr_set_desc isar2 __initconst = {
-	.name		= "id_aa64isar2",
-	.override	= &id_aa64isar2_override,
-	.fields		= {
+DEFINE_OVERRIDE(4, isar2, "id_aa64isar2", id_aa64isar2_override,
 		FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT, NULL),
 		FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT, NULL),
-		{}
-	},
-};
+		{});
 
-static const struct ftr_set_desc smfr0 __initconst = {
-	.name		= "id_aa64smfr0",
-	.override	= &id_aa64smfr0_override,
-	.fields		= {
+DEFINE_OVERRIDE(5, smfr0, "id_aa64smfr0", id_aa64smfr0_override,
 		/* FA64 is a one bit field... :-/ */
 		{ "fa64", ID_AA64SMFR0_EL1_FA64_SHIFT, 1, },
-		{}
-	},
-};
+		{});
 
-static const struct ftr_set_desc sw_features __initconst = {
-	.name		= "arm64_sw",
-	.override	= &arm64_sw_feature_override,
-	.fields		= {
+DEFINE_OVERRIDE(6, sw_features, "arm64_sw", arm64_sw_feature_override,
 		FIELD("nokaslr", ARM64_SW_FEATURE_OVERRIDE_NOKASLR, NULL),
-		{}
-	},
-};
+		{});
 
-static const struct ftr_set_desc * const regs[] __initconst = {
-	&mmfr1,
-	&pfr0,
-	&pfr1,
-	&isar1,
-	&isar2,
-	&smfr0,
-	&sw_features,
-};
+/*
+ * regs[] is populated by R_AARCH64_PREL32 directives invisible to the compiler
+ * so it cannot be static or const, or the compiler might try to use constant
+ * propagation on the values.
+ */
+asmlinkage s32 regs[7] __initdata = { [0 ... ARRAY_SIZE(regs) - 1] = S32_MAX };
+
+static struct arm64_ftr_override * __init reg_override(int i)
+{
+	const struct ftr_set_desc *reg = offset_to_ptr(&regs[i]);
+
+	return offset_to_ptr(&reg->override_offset);
+}
 
 static const struct {
 	char	alias[FTR_ALIAS_NAME_LEN];
@@ -193,15 +174,16 @@ static void __init match_options(const char *cmdline)
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		const struct ftr_set_desc *reg = offset_to_ptr(&regs[i]);
 		int f;
 
-		for (f = 0; strlen(regs[i]->fields[f].name); f++) {
-			u64 shift = regs[i]->fields[f].shift;
-			u64 width = regs[i]->fields[f].width ?: 4;
+		for (f = 0; strlen(reg->fields[f].name); f++) {
+			u64 shift = reg->fields[f].shift;
+			u64 width = reg->fields[f].width ?: 4;
 			u64 mask = GENMASK_ULL(shift + width - 1, shift);
 			u64 v;
 
-			if (find_field(cmdline, regs[i], f, &v))
+			if (find_field(cmdline, reg, f, &v))
 				continue;
 
 			/*
@@ -209,16 +191,16 @@ static void __init match_options(const char *cmdline)
 			 * it by setting the value to the all-ones while
 			 * clearing the mask... Yes, this is fragile.
 			 */
-			if (regs[i]->fields[f].filter &&
-			    !regs[i]->fields[f].filter(v)) {
-				regs[i]->override->val  |= mask;
-				regs[i]->override->mask &= ~mask;
+			if (reg->fields[f].filter &&
+			    !reg->fields[f].filter(v)) {
+				reg_override(i)->val  |= mask;
+				reg_override(i)->mask &= ~mask;
 				continue;
 			}
 
-			regs[i]->override->val  &= ~mask;
-			regs[i]->override->val  |= (v << shift) & mask;
-			regs[i]->override->mask |= mask;
+			reg_override(i)->val  &= ~mask;
+			reg_override(i)->val  |= (v << shift) & mask;
+			reg_override(i)->mask |= mask;
 
 			return;
 		}
@@ -295,8 +277,8 @@ asmlinkage void __init init_feature_override(u64 boot_status)
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(regs); i++) {
-		regs[i]->override->val  = 0;
-		regs[i]->override->mask = 0;
+		reg_override(i)->val  = 0;
+		reg_override(i)->mask = 0;
 	}
 
 	__boot_status = boot_status;
@@ -304,8 +286,8 @@ asmlinkage void __init init_feature_override(u64 boot_status)
 	parse_cmdline();
 
 	for (i = 0; i < ARRAY_SIZE(regs); i++) {
-		dcache_clean_inval_poc((unsigned long)regs[i]->override,
-				       (unsigned long)regs[i]->override +
-				       sizeof(*regs[i]->override));
+		dcache_clean_inval_poc((unsigned long)reg_override(i),
+				       (unsigned long)reg_override(i) +
+				       sizeof(struct arm64_ftr_override));
 	}
 }
-- 
2.35.1




More information about the linux-arm-kernel mailing list