1 // SPDX-License-Identifier: GPL-2.0
3 * Early cpufeature override framework
5 * Copyright (C) 2020 Google LLC
6 * Author: Marc Zyngier <maz@kernel.org>
9 #include <linux/ctype.h>
10 #include <linux/kernel.h>
11 #include <linux/libfdt.h>
13 #include <asm/cacheflush.h>
14 #include <asm/cpufeature.h>
15 #include <asm/setup.h>
19 #define FTR_DESC_NAME_LEN 20
20 #define FTR_DESC_FIELD_LEN 10
21 #define FTR_ALIAS_NAME_LEN 30
22 #define FTR_ALIAS_OPTION_LEN 116
24 static u64 __boot_status __initdata
;
26 typedef bool filter_t(u64 val
);
29 char name
[FTR_DESC_NAME_LEN
];
30 PREL64(struct arm64_ftr_override
, override
);
32 char name
[FTR_DESC_FIELD_LEN
];
35 PREL64(filter_t
, filter
);
39 #define FIELD(n, s, f) { .name = n, .shift = s, .width = 4, .filter = f }
41 static const struct ftr_set_desc mmfr0 __prel64_initconst
= {
42 .name
= "id_aa64mmfr0",
43 .override
= &id_aa64mmfr0_override
,
45 FIELD("ecv", ID_AA64MMFR0_EL1_ECV_SHIFT
, NULL
),
50 static bool __init
mmfr1_vh_filter(u64 val
)
53 * If we ever reach this point while running VHE, we're
54 * guaranteed to be on one of these funky, VHE-stuck CPUs. If
55 * the user was trying to force nVHE on us, proceed with
56 * attitude adjustment.
58 return !(__boot_status
== (BOOT_CPU_FLAG_E2H
| BOOT_CPU_MODE_EL2
) &&
62 static const struct ftr_set_desc mmfr1 __prel64_initconst
= {
63 .name
= "id_aa64mmfr1",
64 .override
= &id_aa64mmfr1_override
,
66 FIELD("vh", ID_AA64MMFR1_EL1_VH_SHIFT
, mmfr1_vh_filter
),
72 static bool __init
mmfr2_varange_filter(u64 val
)
74 int __maybe_unused feat
;
79 #ifdef CONFIG_ARM64_LPA2
80 feat
= cpuid_feature_extract_signed_field(read_sysreg(id_aa64mmfr0_el1
),
81 ID_AA64MMFR0_EL1_TGRAN_SHIFT
);
82 if (feat
>= ID_AA64MMFR0_EL1_TGRAN_LPA2
) {
83 id_aa64mmfr0_override
.val
|=
84 (ID_AA64MMFR0_EL1_TGRAN_LPA2
- 1) << ID_AA64MMFR0_EL1_TGRAN_SHIFT
;
85 id_aa64mmfr0_override
.mask
|= 0xfU
<< ID_AA64MMFR0_EL1_TGRAN_SHIFT
;
91 static const struct ftr_set_desc mmfr2 __prel64_initconst
= {
92 .name
= "id_aa64mmfr2",
93 .override
= &id_aa64mmfr2_override
,
95 FIELD("varange", ID_AA64MMFR2_EL1_VARange_SHIFT
, mmfr2_varange_filter
),
100 static bool __init
pfr0_sve_filter(u64 val
)
103 * Disabling SVE also means disabling all the features that
104 * are associated with it. The easiest way to do it is just to
105 * override id_aa64zfr0_el1 to be 0.
108 id_aa64zfr0_override
.val
= 0;
109 id_aa64zfr0_override
.mask
= GENMASK(63, 0);
115 static const struct ftr_set_desc pfr0 __prel64_initconst
= {
116 .name
= "id_aa64pfr0",
117 .override
= &id_aa64pfr0_override
,
119 FIELD("sve", ID_AA64PFR0_EL1_SVE_SHIFT
, pfr0_sve_filter
),
120 FIELD("el0", ID_AA64PFR0_EL1_EL0_SHIFT
, NULL
),
125 static bool __init
pfr1_sme_filter(u64 val
)
128 * Similarly to SVE, disabling SME also means disabling all
129 * the features that are associated with it. Just set
130 * id_aa64smfr0_el1 to 0 and don't look back.
133 id_aa64smfr0_override
.val
= 0;
134 id_aa64smfr0_override
.mask
= GENMASK(63, 0);
140 static const struct ftr_set_desc pfr1 __prel64_initconst
= {
141 .name
= "id_aa64pfr1",
142 .override
= &id_aa64pfr1_override
,
144 FIELD("bt", ID_AA64PFR1_EL1_BT_SHIFT
, NULL
),
145 FIELD("gcs", ID_AA64PFR1_EL1_GCS_SHIFT
, NULL
),
146 FIELD("mte", ID_AA64PFR1_EL1_MTE_SHIFT
, NULL
),
147 FIELD("sme", ID_AA64PFR1_EL1_SME_SHIFT
, pfr1_sme_filter
),
152 static const struct ftr_set_desc isar1 __prel64_initconst
= {
153 .name
= "id_aa64isar1",
154 .override
= &id_aa64isar1_override
,
156 FIELD("gpi", ID_AA64ISAR1_EL1_GPI_SHIFT
, NULL
),
157 FIELD("gpa", ID_AA64ISAR1_EL1_GPA_SHIFT
, NULL
),
158 FIELD("api", ID_AA64ISAR1_EL1_API_SHIFT
, NULL
),
159 FIELD("apa", ID_AA64ISAR1_EL1_APA_SHIFT
, NULL
),
164 static const struct ftr_set_desc isar2 __prel64_initconst
= {
165 .name
= "id_aa64isar2",
166 .override
= &id_aa64isar2_override
,
168 FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT
, NULL
),
169 FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT
, NULL
),
170 FIELD("mops", ID_AA64ISAR2_EL1_MOPS_SHIFT
, NULL
),
175 static const struct ftr_set_desc smfr0 __prel64_initconst
= {
176 .name
= "id_aa64smfr0",
177 .override
= &id_aa64smfr0_override
,
179 FIELD("smever", ID_AA64SMFR0_EL1_SMEver_SHIFT
, NULL
),
180 /* FA64 is a one bit field... :-/ */
181 { "fa64", ID_AA64SMFR0_EL1_FA64_SHIFT
, 1, },
186 static bool __init
hvhe_filter(u64 val
)
188 u64 mmfr1
= read_sysreg(id_aa64mmfr1_el1
);
191 lower_32_bits(__boot_status
) == BOOT_CPU_MODE_EL2
&&
192 cpuid_feature_extract_unsigned_field(mmfr1
,
193 ID_AA64MMFR1_EL1_VH_SHIFT
));
196 static const struct ftr_set_desc sw_features __prel64_initconst
= {
198 .override
= &arm64_sw_feature_override
,
200 FIELD("nokaslr", ARM64_SW_FEATURE_OVERRIDE_NOKASLR
, NULL
),
201 FIELD("hvhe", ARM64_SW_FEATURE_OVERRIDE_HVHE
, hvhe_filter
),
202 FIELD("rodataoff", ARM64_SW_FEATURE_OVERRIDE_RODATA_OFF
, NULL
),
208 PREL64(const struct ftr_set_desc
, reg
) regs
[] __prel64_initconst
= {
220 static const struct {
221 char alias
[FTR_ALIAS_NAME_LEN
];
222 char feature
[FTR_ALIAS_OPTION_LEN
];
223 } aliases
[] __initconst
= {
224 { "kvm_arm.mode=nvhe", "arm64_sw.hvhe=0 id_aa64mmfr1.vh=0" },
225 { "kvm_arm.mode=protected", "arm64_sw.hvhe=1" },
226 { "arm64.nosve", "id_aa64pfr0.sve=0" },
227 { "arm64.nosme", "id_aa64pfr1.sme=0" },
228 { "arm64.nobti", "id_aa64pfr1.bt=0" },
229 { "arm64.nogcs", "id_aa64pfr1.gcs=0" },
231 "id_aa64isar1.gpi=0 id_aa64isar1.gpa=0 "
232 "id_aa64isar1.api=0 id_aa64isar1.apa=0 "
233 "id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" },
234 { "arm64.nomops", "id_aa64isar2.mops=0" },
235 { "arm64.nomte", "id_aa64pfr1.mte=0" },
236 { "nokaslr", "arm64_sw.nokaslr=1" },
237 { "rodata=off", "arm64_sw.rodataoff=1" },
238 { "arm64.nolva", "id_aa64mmfr2.varange=0" },
239 { "arm64.no32bit_el0", "id_aa64pfr0.el0=1" },
242 static int __init
parse_hexdigit(const char *p
, u64
*v
)
244 // skip "0x" if it comes next
245 if (p
[0] == '0' && tolower(p
[1]) == 'x')
248 // check whether the RHS is a single hex digit
249 if (!isxdigit(p
[0]) || (p
[1] && !isspace(p
[1])))
252 *v
= tolower(*p
) - (isdigit(*p
) ? '0' : 'a' - 10);
256 static int __init
find_field(const char *cmdline
, char *opt
, int len
,
257 const struct ftr_set_desc
*reg
, int f
, u64
*v
)
259 int flen
= strlen(reg
->fields
[f
].name
);
261 // append '<fieldname>=' to obtain '<name>.<fieldname>='
262 memcpy(opt
+ len
, reg
->fields
[f
].name
, flen
);
266 if (memcmp(cmdline
, opt
, len
))
269 return parse_hexdigit(cmdline
+ len
, v
);
272 static void __init
match_options(const char *cmdline
)
274 char opt
[FTR_DESC_NAME_LEN
+ FTR_DESC_FIELD_LEN
+ 2];
277 for (i
= 0; i
< ARRAY_SIZE(regs
); i
++) {
278 const struct ftr_set_desc
*reg
= prel64_pointer(regs
[i
].reg
);
279 struct arm64_ftr_override
*override
;
280 int len
= strlen(reg
->name
);
283 override
= prel64_pointer(reg
->override
);
285 // set opt[] to '<name>.'
286 memcpy(opt
, reg
->name
, len
);
289 for (f
= 0; reg
->fields
[f
].name
[0] != '\0'; f
++) {
290 u64 shift
= reg
->fields
[f
].shift
;
291 u64 width
= reg
->fields
[f
].width
?: 4;
292 u64 mask
= GENMASK_ULL(shift
+ width
- 1, shift
);
293 bool (*filter
)(u64 val
);
296 if (find_field(cmdline
, opt
, len
, reg
, f
, &v
))
300 * If an override gets filtered out, advertise
301 * it by setting the value to the all-ones while
302 * clearing the mask... Yes, this is fragile.
304 filter
= prel64_pointer(reg
->fields
[f
].filter
);
305 if (filter
&& !filter(v
)) {
306 override
->val
|= mask
;
307 override
->mask
&= ~mask
;
311 override
->val
&= ~mask
;
312 override
->val
|= (v
<< shift
) & mask
;
313 override
->mask
|= mask
;
320 static __init
void __parse_cmdline(const char *cmdline
, bool parse_aliases
)
327 cmdline
= skip_spaces(cmdline
);
329 /* terminate on "--" appearing on the command line by itself */
330 if (cmdline
[0] == '-' && cmdline
[1] == '-' && isspace(cmdline
[2]))
333 for (len
= 0; cmdline
[len
] && !isspace(cmdline
[len
]); len
++) {
334 if (len
>= sizeof(buf
) - 1)
336 if (cmdline
[len
] == '-')
339 buf
[len
] = cmdline
[len
];
350 for (i
= 0; parse_aliases
&& i
< ARRAY_SIZE(aliases
); i
++)
351 if (!memcmp(buf
, aliases
[i
].alias
, len
+ 1))
352 __parse_cmdline(aliases
[i
].feature
, false);
356 static __init
const u8
*get_bootargs_cmdline(const void *fdt
, int node
)
358 static char const bootargs
[] __initconst
= "bootargs";
364 prop
= fdt_getprop(fdt
, node
, bootargs
, NULL
);
368 return strlen(prop
) ? prop
: NULL
;
371 static __init
void parse_cmdline(const void *fdt
, int chosen
)
373 static char const cmdline
[] __initconst
= CONFIG_CMDLINE
;
374 const u8
*prop
= get_bootargs_cmdline(fdt
, chosen
);
376 if (IS_ENABLED(CONFIG_CMDLINE_FORCE
) || !prop
)
377 __parse_cmdline(cmdline
, true);
379 if (!IS_ENABLED(CONFIG_CMDLINE_FORCE
) && prop
)
380 __parse_cmdline(prop
, true);
383 void __init
init_feature_override(u64 boot_status
, const void *fdt
,
386 struct arm64_ftr_override
*override
;
387 const struct ftr_set_desc
*reg
;
390 for (i
= 0; i
< ARRAY_SIZE(regs
); i
++) {
391 reg
= prel64_pointer(regs
[i
].reg
);
392 override
= prel64_pointer(reg
->override
);
398 __boot_status
= boot_status
;
400 parse_cmdline(fdt
, chosen
);
402 for (i
= 0; i
< ARRAY_SIZE(regs
); i
++) {
403 reg
= prel64_pointer(regs
[i
].reg
);
404 override
= prel64_pointer(reg
->override
);
405 dcache_clean_inval_poc((unsigned long)override
,
406 (unsigned long)(override
+ 1));
410 char * __init
skip_spaces(const char *str
)
412 while (isspace(*str
))