@@ -6,6 +6,7 @@
* Author: Marc Zyngier <maz@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(®s[i]);
+
+ return offset_to_ptr(®->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(®s[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));
}
}
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@kernel.org> --- arch/arm64/kernel/idreg-override.c | 144 +++++++++----------- 1 file changed, 63 insertions(+), 81 deletions(-)