2021-02-08 09:57:22 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* Early cpufeature override framework
*
* Copyright ( C ) 2020 Google LLC
* Author : Marc Zyngier < maz @ kernel . org >
*/
# include <linux/ctype.h>
# include <linux/kernel.h>
# include <linux/libfdt.h>
# include <asm/cacheflush.h>
2021-02-08 09:57:23 +00:00
# include <asm/cpufeature.h>
2021-02-08 09:57:22 +00:00
# include <asm/setup.h>
# define FTR_DESC_NAME_LEN 20
# define FTR_DESC_FIELD_LEN 10
2021-02-08 09:57:25 +00:00
# define FTR_ALIAS_NAME_LEN 30
2022-02-24 12:49:52 +00:00
# define FTR_ALIAS_OPTION_LEN 116
2021-02-08 09:57:22 +00:00
2022-06-30 17:04:53 +01:00
static u64 __boot_status __initdata ;
2023-11-29 12:16:11 +01:00
// temporary __prel64 related definitions
// to be removed when this code is moved under pi/
# define __prel64_initconst __initconst
# define PREL64(type, name) union { type *name; }
# define prel64_pointer(__d) (__d)
typedef bool filter_t ( u64 val ) ;
2021-02-08 09:57:22 +00:00
struct ftr_set_desc {
char name [ FTR_DESC_NAME_LEN ] ;
2023-11-29 12:16:11 +01:00
PREL64 ( struct arm64_ftr_override , override ) ;
2021-02-08 09:57:22 +00:00
struct {
char name [ FTR_DESC_FIELD_LEN ] ;
u8 shift ;
2022-06-30 17:04:56 +01:00
u8 width ;
2023-11-29 12:16:11 +01:00
PREL64 ( filter_t , filter ) ;
2021-02-08 09:57:22 +00:00
} fields [ ] ;
} ;
2022-06-30 17:04:56 +01:00
# define FIELD(n, s, f) { .name = n, .shift = s, .width = 4, .filter = f }
2021-04-08 14:10:09 +01:00
static bool __init mmfr1_vh_filter ( u64 val )
{
/*
* If we ever reach this point while running VHE , we ' re
* guaranteed to be on one of these funky , VHE - stuck CPUs . If
* the user was trying to force nVHE on us , proceed with
* attitude adjustment .
*/
2022-06-30 17:04:53 +01:00
return ! ( __boot_status = = ( BOOT_CPU_FLAG_E2H | BOOT_CPU_MODE_EL2 ) & &
val = = 0 ) ;
2021-04-08 14:10:09 +01:00
}
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc mmfr1 __prel64_initconst = {
2021-02-08 09:57:23 +00:00
. name = " id_aa64mmfr1 " ,
. override = & id_aa64mmfr1_override ,
. fields = {
2022-09-05 23:54:07 +01:00
FIELD ( " vh " , ID_AA64MMFR1_EL1_VH_SHIFT , mmfr1_vh_filter ) ,
2021-02-08 09:57:23 +00:00
{ }
} ,
} ;
2022-06-30 17:04:59 +01:00
static bool __init pfr0_sve_filter ( u64 val )
{
/*
* Disabling SVE also means disabling all the features that
* are associated with it . The easiest way to do it is just to
* override id_aa64zfr0_el1 to be 0.
*/
if ( ! val ) {
id_aa64zfr0_override . val = 0 ;
id_aa64zfr0_override . mask = GENMASK ( 63 , 0 ) ;
}
return true ;
}
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc pfr0 __prel64_initconst = {
2022-06-30 17:04:59 +01:00
. name = " id_aa64pfr0 " ,
. override = & id_aa64pfr0_override ,
. fields = {
2022-09-05 23:54:03 +01:00
FIELD ( " sve " , ID_AA64PFR0_EL1_SVE_SHIFT , pfr0_sve_filter ) ,
2021-02-08 09:57:23 +00:00
{ }
} ,
} ;
2022-06-30 17:04:58 +01:00
static bool __init pfr1_sme_filter ( u64 val )
{
/*
* Similarly to SVE , disabling SME also means disabling all
* the features that are associated with it . Just set
* id_aa64smfr0_el1 to 0 and don ' t look back .
*/
if ( ! val ) {
id_aa64smfr0_override . val = 0 ;
id_aa64smfr0_override . mask = GENMASK ( 63 , 0 ) ;
}
return true ;
}
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc pfr1 __prel64_initconst = {
2021-02-08 09:57:29 +00:00
. name = " id_aa64pfr1 " ,
. override = & id_aa64pfr1_override ,
. fields = {
2022-09-05 23:54:04 +01:00
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 ) ,
2021-02-08 09:57:29 +00:00
{ }
} ,
} ;
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc isar1 __prel64_initconst = {
2021-02-08 09:57:31 +00:00
. name = " id_aa64isar1 " ,
. override = & id_aa64isar1_override ,
. fields = {
2022-07-25 10:59:15 +01:00
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 ) ,
2021-02-08 09:57:31 +00:00
{ }
} ,
} ;
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc isar2 __prel64_initconst = {
2022-02-24 12:49:52 +00:00
. name = " id_aa64isar2 " ,
. override = & id_aa64isar2_override ,
. fields = {
2022-07-25 10:59:15 +01:00
FIELD ( " gpa3 " , ID_AA64ISAR2_EL1_GPA3_SHIFT , NULL ) ,
FIELD ( " apa3 " , ID_AA64ISAR2_EL1_APA3_SHIFT , NULL ) ,
2023-05-09 15:22:34 +01:00
FIELD ( " mops " , ID_AA64ISAR2_EL1_MOPS_SHIFT , NULL ) ,
2022-02-24 12:49:52 +00:00
{ }
} ,
} ;
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc smfr0 __prel64_initconst = {
2022-06-30 17:05:00 +01:00
. name = " id_aa64smfr0 " ,
. override = & id_aa64smfr0_override ,
. fields = {
2023-01-16 16:04:42 +00:00
FIELD ( " smever " , ID_AA64SMFR0_EL1_SMEver_SHIFT , NULL ) ,
2022-06-30 17:05:00 +01:00
/* FA64 is a one bit field... :-/ */
2022-07-25 10:59:15 +01:00
{ " fa64 " , ID_AA64SMFR0_EL1_FA64_SHIFT , 1 , } ,
2022-02-24 12:49:52 +00:00
{ }
} ,
} ;
2023-06-09 17:21:59 +01:00
static bool __init hvhe_filter ( u64 val )
{
u64 mmfr1 = read_sysreg ( id_aa64mmfr1_el1 ) ;
return ( val = = 1 & &
lower_32_bits ( __boot_status ) = = BOOT_CPU_MODE_EL2 & &
cpuid_feature_extract_unsigned_field ( mmfr1 ,
ID_AA64MMFR1_EL1_VH_SHIFT ) ) ;
}
2021-02-08 09:57:28 +00:00
2023-11-29 12:16:11 +01:00
static const struct ftr_set_desc sw_features __prel64_initconst = {
2023-06-09 17:21:46 +01:00
. name = " arm64_sw " ,
. override = & arm64_sw_feature_override ,
2021-02-08 09:57:28 +00:00
. fields = {
2023-06-09 17:21:46 +01:00
FIELD ( " nokaslr " , ARM64_SW_FEATURE_OVERRIDE_NOKASLR , NULL ) ,
2023-06-09 17:21:59 +01:00
FIELD ( " hvhe " , ARM64_SW_FEATURE_OVERRIDE_HVHE , hvhe_filter ) ,
2021-02-08 09:57:28 +00:00
{ }
} ,
} ;
2023-11-29 12:16:11 +01:00
static const
PREL64 ( const struct ftr_set_desc , reg ) regs [ ] __prel64_initconst = {
{ & mmfr1 } ,
{ & pfr0 } ,
{ & pfr1 } ,
{ & isar1 } ,
{ & isar2 } ,
{ & smfr0 } ,
{ & sw_features } ,
2021-02-08 09:57:22 +00:00
} ;
2021-02-08 09:57:25 +00:00
static const struct {
char alias [ FTR_ALIAS_NAME_LEN ] ;
char feature [ FTR_ALIAS_OPTION_LEN ] ;
} aliases [ ] __initconst = {
2023-11-29 12:16:12 +01:00
{ " kvm_arm.mode=nvhe " , " id_aa64mmfr1.vh=0 " } ,
{ " kvm_arm.mode=protected " , " id_aa64mmfr1.vh=0 " } ,
2023-03-23 22:06:32 +00:00
{ " arm64.nosve " , " id_aa64pfr0.sve=0 " } ,
2022-06-30 17:04:58 +01:00
{ " arm64.nosme " , " id_aa64pfr1.sme=0 " } ,
2021-02-08 09:57:29 +00:00
{ " arm64.nobti " , " id_aa64pfr1.bt=0 " } ,
2021-02-08 09:57:31 +00:00
{ " arm64.nopauth " ,
" id_aa64isar1.gpi=0 id_aa64isar1.gpa=0 "
2022-02-24 12:49:52 +00:00
" id_aa64isar1.api=0 id_aa64isar1.apa=0 "
" id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0 " } ,
2023-05-09 15:22:34 +01:00
{ " arm64.nomops " , " id_aa64isar2.mops=0 " } ,
2021-08-03 15:08:22 +08:00
{ " arm64.nomte " , " id_aa64pfr1.mte=0 " } ,
2023-06-09 17:21:46 +01:00
{ " nokaslr " , " arm64_sw.nokaslr=1 " } ,
2021-02-08 09:57:25 +00:00
} ;
2023-11-29 12:16:15 +01:00
static int __init parse_hexdigit ( const char * p , u64 * v )
{
// skip "0x" if it comes next
if ( p [ 0 ] = = ' 0 ' & & tolower ( p [ 1 ] ) = = ' x ' )
p + = 2 ;
// check whether the RHS is a single hex digit
if ( ! isxdigit ( p [ 0 ] ) | | ( p [ 1 ] & & ! isspace ( p [ 1 ] ) ) )
return - EINVAL ;
* v = tolower ( * p ) - ( isdigit ( * p ) ? ' 0 ' : ' a ' - 10 ) ;
return 0 ;
}
2023-11-29 12:16:14 +01:00
static int __init find_field ( const char * cmdline , char * opt , int len ,
2021-02-08 09:57:22 +00:00
const struct ftr_set_desc * reg , int f , u64 * v )
{
2023-11-29 12:16:14 +01:00
int flen = strlen ( reg - > fields [ f ] . name ) ;
2021-02-08 09:57:22 +00:00
2023-11-29 12:16:14 +01:00
// append '<fieldname>=' to obtain '<name>.<fieldname>='
memcpy ( opt + len , reg - > fields [ f ] . name , flen ) ;
len + = flen ;
opt [ len + + ] = ' = ' ;
2021-02-08 09:57:22 +00:00
2023-11-29 12:16:12 +01:00
if ( memcmp ( cmdline , opt , len ) )
2021-02-08 09:57:22 +00:00
return - 1 ;
2023-11-29 12:16:15 +01:00
return parse_hexdigit ( cmdline + len , v ) ;
2021-02-08 09:57:22 +00:00
}
static void __init match_options ( const char * cmdline )
{
2023-11-29 12:16:14 +01:00
char opt [ FTR_DESC_NAME_LEN + FTR_DESC_FIELD_LEN + 2 ] ;
2021-02-08 09:57:22 +00:00
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( regs ) ; i + + ) {
2023-11-29 12:16:11 +01:00
const struct ftr_set_desc * reg = prel64_pointer ( regs [ i ] . reg ) ;
struct arm64_ftr_override * override ;
2023-11-29 12:16:14 +01:00
int len = strlen ( reg - > name ) ;
2021-02-08 09:57:22 +00:00
int f ;
2023-11-29 12:16:11 +01:00
override = prel64_pointer ( reg - > override ) ;
2023-11-29 12:16:14 +01:00
// set opt[] to '<name>.'
memcpy ( opt , reg - > name , len ) ;
opt [ len + + ] = ' . ' ;
2023-11-29 12:16:13 +01:00
for ( f = 0 ; reg - > fields [ f ] . name [ 0 ] ! = ' \0 ' ; f + + ) {
2023-11-29 12:16:11 +01:00
u64 shift = reg - > fields [ f ] . shift ;
u64 width = reg - > fields [ f ] . width ? : 4 ;
2022-06-30 17:04:56 +01:00
u64 mask = GENMASK_ULL ( shift + width - 1 , shift ) ;
2023-11-29 12:16:11 +01:00
bool ( * filter ) ( u64 val ) ;
2021-02-08 09:57:22 +00:00
u64 v ;
2023-11-29 12:16:14 +01:00
if ( find_field ( cmdline , opt , len , reg , f , & v ) )
2021-02-08 09:57:22 +00:00
continue ;
2021-04-08 14:10:08 +01:00
/*
* If an override gets filtered out , advertise
2022-06-30 17:04:56 +01:00
* it by setting the value to the all - ones while
2021-04-08 14:10:08 +01:00
* clearing the mask . . . Yes , this is fragile .
*/
2023-11-29 12:16:11 +01:00
filter = prel64_pointer ( reg - > fields [ f ] . filter ) ;
if ( filter & & ! filter ( v ) ) {
override - > val | = mask ;
override - > mask & = ~ mask ;
2021-04-08 14:10:08 +01:00
continue ;
}
2023-11-29 12:16:11 +01:00
override - > val & = ~ mask ;
override - > val | = ( v < < shift ) & mask ;
override - > mask | = mask ;
2021-02-08 09:57:22 +00:00
return ;
}
}
}
2021-02-08 09:57:25 +00:00
static __init void __parse_cmdline ( const char * cmdline , bool parse_aliases )
2021-02-08 09:57:22 +00:00
{
do {
char buf [ 256 ] ;
size_t len ;
int i ;
cmdline = skip_spaces ( cmdline ) ;
2023-11-29 12:16:12 +01:00
/* terminate on "--" appearing on the command line by itself */
if ( cmdline [ 0 ] = = ' - ' & & cmdline [ 1 ] = = ' - ' & & isspace ( cmdline [ 2 ] ) )
2021-02-08 09:57:22 +00:00
return ;
2023-11-29 12:16:12 +01:00
for ( len = 0 ; cmdline [ len ] & & ! isspace ( cmdline [ len ] ) ; len + + ) {
if ( len > = sizeof ( buf ) - 1 )
break ;
if ( cmdline [ len ] = = ' - ' )
buf [ len ] = ' _ ' ;
else
buf [ len ] = cmdline [ len ] ;
}
if ( ! len )
2021-02-08 09:57:22 +00:00
return ;
2023-11-29 12:16:12 +01:00
buf [ len ] = 0 ;
2021-02-08 09:57:22 +00:00
cmdline + = len ;
match_options ( buf ) ;
2021-02-08 09:57:25 +00:00
for ( i = 0 ; parse_aliases & & i < ARRAY_SIZE ( aliases ) ; i + + )
2023-11-29 12:16:12 +01:00
if ( ! memcmp ( buf , aliases [ i ] . alias , len + 1 ) )
2021-02-08 09:57:25 +00:00
__parse_cmdline ( aliases [ i ] . feature , false ) ;
2021-02-08 09:57:22 +00:00
} while ( 1 ) ;
}
2021-03-03 13:49:26 +00:00
static __init const u8 * get_bootargs_cmdline ( void )
2021-02-08 09:57:22 +00:00
{
2021-03-03 13:49:26 +00:00
const u8 * prop ;
void * fdt ;
int node ;
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
fdt = get_early_fdt_ptr ( ) ;
if ( ! fdt )
return NULL ;
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
node = fdt_path_offset ( fdt , " /chosen " ) ;
if ( node < 0 )
return NULL ;
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
prop = fdt_getprop ( fdt , node , " bootargs " , NULL ) ;
if ( ! prop )
return NULL ;
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
return strlen ( prop ) ? prop : NULL ;
}
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
static __init void parse_cmdline ( void )
{
const u8 * prop = get_bootargs_cmdline ( ) ;
2021-03-03 13:49:27 +00:00
if ( IS_ENABLED ( CONFIG_CMDLINE_FORCE ) | | ! prop )
2021-03-03 13:49:26 +00:00
__parse_cmdline ( CONFIG_CMDLINE , true ) ;
2021-02-08 09:57:22 +00:00
2021-03-03 13:49:26 +00:00
if ( ! IS_ENABLED ( CONFIG_CMDLINE_FORCE ) & & prop )
__parse_cmdline ( prop , true ) ;
2021-02-08 09:57:22 +00:00
}
/* Keep checkers quiet */
2022-07-13 15:09:49 +01:00
void init_feature_override ( u64 boot_status ) ;
2021-02-08 09:57:22 +00:00
2022-07-13 15:09:49 +01:00
asmlinkage void __init init_feature_override ( u64 boot_status )
2021-02-08 09:57:22 +00:00
{
2023-11-29 12:16:11 +01:00
struct arm64_ftr_override * override ;
const struct ftr_set_desc * reg ;
2021-02-08 09:57:22 +00:00
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( regs ) ; i + + ) {
2023-11-29 12:16:11 +01:00
reg = prel64_pointer ( regs [ i ] . reg ) ;
override = prel64_pointer ( reg - > override ) ;
override - > val = 0 ;
override - > mask = 0 ;
2021-02-08 09:57:22 +00:00
}
2022-06-30 17:04:53 +01:00
__boot_status = boot_status ;
2021-02-08 09:57:22 +00:00
parse_cmdline ( ) ;
for ( i = 0 ; i < ARRAY_SIZE ( regs ) ; i + + ) {
2023-11-29 12:16:11 +01:00
reg = prel64_pointer ( regs [ i ] . reg ) ;
override = prel64_pointer ( reg - > override ) ;
dcache_clean_inval_poc ( ( unsigned long ) override ,
( unsigned long ) ( override + 1 ) ) ;
2021-02-08 09:57:22 +00:00
}
}