2018-01-26 14:46:47 +03:00
// SPDX-License-Identifier: GPL-2.0
# include <linux/module.h>
2018-03-21 11:38:21 +03:00
# include <linux/device.h>
2018-01-26 14:46:47 +03:00
# include <asm/nospec-branch.h>
2018-03-23 19:09:39 +03:00
static int __init nobp_setup_early ( char * str )
{
bool enabled ;
int rc ;
rc = kstrtobool ( str , & enabled ) ;
if ( rc )
return rc ;
2018-03-23 15:04:49 +03:00
if ( enabled & & test_facility ( 82 ) ) {
/*
* The user explicitely requested nobp = 1 , enable it and
* disable the expoline support .
*/
2018-03-23 19:09:39 +03:00
__set_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) ;
2018-03-23 15:04:49 +03:00
if ( IS_ENABLED ( CONFIG_EXPOLINE ) )
nospec_disable = 1 ;
} else {
2018-03-23 19:09:39 +03:00
__clear_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) ;
2018-03-23 15:04:49 +03:00
}
2018-03-23 19:09:39 +03:00
return 0 ;
}
early_param ( " nobp " , nobp_setup_early ) ;
static int __init nospec_setup_early ( char * str )
{
__clear_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) ;
return 0 ;
}
early_param ( " nospec " , nospec_setup_early ) ;
2018-03-20 18:33:43 +03:00
static int __init nospec_report ( void )
{
if ( IS_ENABLED ( CC_USING_EXPOLINE ) & & ! nospec_disable )
2018-05-09 10:47:53 +03:00
pr_info ( " Spectre V2 mitigation: execute trampolines \n " ) ;
2018-03-20 18:33:43 +03:00
if ( __test_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) )
2018-05-09 10:47:53 +03:00
pr_info ( " Spectre V2 mitigation: limited branch prediction \n " ) ;
2018-03-20 18:33:43 +03:00
return 0 ;
}
arch_initcall ( nospec_report ) ;
2018-03-23 19:09:39 +03:00
# ifdef CONFIG_EXPOLINE
2018-03-23 15:04:49 +03:00
int nospec_disable = IS_ENABLED ( CONFIG_EXPOLINE_OFF ) ;
2018-01-26 14:46:47 +03:00
static int __init nospectre_v2_setup_early ( char * str )
{
2018-03-23 15:04:49 +03:00
nospec_disable = 1 ;
2018-01-26 14:46:47 +03:00
return 0 ;
}
early_param ( " nospectre_v2 " , nospectre_v2_setup_early ) ;
2018-04-11 09:35:23 +03:00
void __init nospec_auto_detect ( void )
2018-03-23 15:04:49 +03:00
{
if ( IS_ENABLED ( CC_USING_EXPOLINE ) ) {
/*
* The kernel has been compiled with expolines .
* Keep expolines enabled and disable nobp .
*/
nospec_disable = 0 ;
__clear_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) ;
}
/*
* If the kernel has not been compiled with expolines the
* nobp setting decides what is done , this depends on the
* CONFIG_KERNEL_NP option and the nobp / nospec parameters .
*/
}
2018-01-26 14:46:47 +03:00
static int __init spectre_v2_setup_early ( char * str )
{
if ( str & & ! strncmp ( str , " on " , 2 ) ) {
2018-03-23 15:04:49 +03:00
nospec_disable = 0 ;
__clear_facility ( 82 , S390_lowcore . alt_stfle_fac_list ) ;
2018-01-26 14:46:47 +03:00
}
2018-03-23 15:04:49 +03:00
if ( str & & ! strncmp ( str , " off " , 3 ) )
nospec_disable = 1 ;
if ( str & & ! strncmp ( str , " auto " , 4 ) )
2018-04-11 09:35:23 +03:00
nospec_auto_detect ( ) ;
2018-01-26 14:46:47 +03:00
return 0 ;
}
early_param ( " spectre_v2 " , spectre_v2_setup_early ) ;
static void __init_or_module __nospec_revert ( s32 * start , s32 * end )
{
enum { BRCL_EXPOLINE , BRASL_EXPOLINE } type ;
u8 * instr , * thunk , * br ;
u8 insnbuf [ 6 ] ;
s32 * epo ;
/* Second part of the instruction replace is always a nop */
for ( epo = start ; epo < end ; epo + + ) {
instr = ( u8 * ) epo + * epo ;
if ( instr [ 0 ] = = 0xc0 & & ( instr [ 1 ] & 0x0f ) = = 0x04 )
type = BRCL_EXPOLINE ; /* brcl instruction */
else if ( instr [ 0 ] = = 0xc0 & & ( instr [ 1 ] & 0x0f ) = = 0x05 )
type = BRASL_EXPOLINE ; /* brasl instruction */
else
continue ;
thunk = instr + ( * ( int * ) ( instr + 2 ) ) * 2 ;
if ( thunk [ 0 ] = = 0xc6 & & thunk [ 1 ] = = 0x00 )
/* exrl %r0,<target-br> */
br = thunk + ( * ( int * ) ( thunk + 2 ) ) * 2 ;
else if ( thunk [ 0 ] = = 0xc0 & & ( thunk [ 1 ] & 0x0f ) = = 0x00 & &
thunk [ 6 ] = = 0x44 & & thunk [ 7 ] = = 0x00 & &
( thunk [ 8 ] & 0x0f ) = = 0x00 & & thunk [ 9 ] = = 0x00 & &
( thunk [ 1 ] & 0xf0 ) = = ( thunk [ 8 ] & 0xf0 ) )
/* larl %rx,<target br> + ex %r0,0(%rx) */
br = thunk + ( * ( int * ) ( thunk + 2 ) ) * 2 ;
else
continue ;
2018-04-24 16:32:08 +03:00
/* Check for unconditional branch 0x07f? or 0x47f???? */
if ( ( br [ 0 ] & 0xbf ) ! = 0x07 | | ( br [ 1 ] & 0xf0 ) ! = 0xf0 )
2018-01-26 14:46:47 +03:00
continue ;
2018-04-24 16:32:08 +03:00
memcpy ( insnbuf + 2 , ( char [ ] ) { 0x47 , 0x00 , 0x07 , 0x00 } , 4 ) ;
2018-01-26 14:46:47 +03:00
switch ( type ) {
case BRCL_EXPOLINE :
insnbuf [ 0 ] = br [ 0 ] ;
insnbuf [ 1 ] = ( instr [ 1 ] & 0xf0 ) | ( br [ 1 ] & 0x0f ) ;
2018-04-24 16:32:08 +03:00
if ( br [ 0 ] = = 0x47 ) {
/* brcl to b, replace with bc + nopr */
insnbuf [ 2 ] = br [ 2 ] ;
insnbuf [ 3 ] = br [ 3 ] ;
} else {
/* brcl to br, replace with bcr + nop */
}
2018-01-26 14:46:47 +03:00
break ;
case BRASL_EXPOLINE :
insnbuf [ 1 ] = ( instr [ 1 ] & 0xf0 ) | ( br [ 1 ] & 0x0f ) ;
2018-04-24 16:32:08 +03:00
if ( br [ 0 ] = = 0x47 ) {
/* brasl to b, replace with bas + nopr */
insnbuf [ 0 ] = 0x4d ;
insnbuf [ 2 ] = br [ 2 ] ;
insnbuf [ 3 ] = br [ 3 ] ;
} else {
/* brasl to br, replace with basr + nop */
insnbuf [ 0 ] = 0x0d ;
}
2018-01-26 14:46:47 +03:00
break ;
}
s390_kernel_write ( instr , insnbuf , 6 ) ;
}
}
2018-03-23 15:04:49 +03:00
void __init_or_module nospec_revert ( s32 * start , s32 * end )
2018-01-26 14:46:47 +03:00
{
2018-03-23 15:04:49 +03:00
if ( nospec_disable )
2018-01-26 14:46:47 +03:00
__nospec_revert ( start , end ) ;
}
extern s32 __nospec_call_start [ ] , __nospec_call_end [ ] ;
extern s32 __nospec_return_start [ ] , __nospec_return_end [ ] ;
void __init nospec_init_branches ( void )
{
2018-03-23 15:04:49 +03:00
nospec_revert ( __nospec_call_start , __nospec_call_end ) ;
nospec_revert ( __nospec_return_start , __nospec_return_end ) ;
2018-01-26 14:46:47 +03:00
}
2018-03-23 19:09:39 +03:00
# endif /* CONFIG_EXPOLINE */