2014-11-14 18:54:08 +03:00
/*
* alternative runtime patching
* inspired by the x86 version
*
* Copyright ( C ) 2014 ARM Ltd .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# define pr_fmt(fmt) "alternatives: " fmt
# include <linux/init.h>
# include <linux/cpu.h>
# include <asm/cacheflush.h>
# include <asm/alternative.h>
# include <asm/cpufeature.h>
2015-06-01 12:47:40 +03:00
# include <asm/insn.h>
2016-08-24 20:27:28 +03:00
# include <asm/sections.h>
2014-11-14 18:54:08 +03:00
# include <linux/stop_machine.h>
2017-06-29 17:40:12 +03:00
# define __ALT_PTR(a,f) ((void *)&(a)->f + (a)->f)
2015-06-01 12:47:40 +03:00
# define ALT_ORIG_PTR(a) __ALT_PTR(a, orig_offset)
# define ALT_REPL_PTR(a) __ALT_PTR(a, alt_offset)
2018-01-08 18:38:06 +03:00
int alternatives_applied ;
2014-11-28 16:40:45 +03:00
struct alt_region {
struct alt_instr * begin ;
struct alt_instr * end ;
} ;
2015-06-01 12:47:40 +03:00
/*
* Check if the target PC is within an alternative block .
*/
static bool branch_insn_requires_update ( struct alt_instr * alt , unsigned long pc )
{
unsigned long replptr ;
if ( kernel_text_address ( pc ) )
2018-08-08 02:59:57 +03:00
return true ;
2015-06-01 12:47:40 +03:00
replptr = ( unsigned long ) ALT_REPL_PTR ( alt ) ;
if ( pc > = replptr & & pc < = ( replptr + alt - > alt_len ) )
2018-08-08 02:59:57 +03:00
return false ;
2015-06-01 12:47:40 +03:00
/*
* Branching into * another * alternate sequence is doomed , and
* we ' re not even trying to fix it up .
*/
BUG ( ) ;
}
2016-09-09 16:07:13 +03:00
# define align_down(x, a) ((unsigned long)(x) & ~(((unsigned long)(a)) - 1))
2017-06-29 17:40:12 +03:00
static u32 get_alt_insn ( struct alt_instr * alt , __le32 * insnptr , __le32 * altinsnptr )
2015-06-01 12:47:40 +03:00
{
u32 insn ;
insn = le32_to_cpu ( * altinsnptr ) ;
if ( aarch64_insn_is_branch_imm ( insn ) ) {
s32 offset = aarch64_get_branch_offset ( insn ) ;
unsigned long target ;
target = ( unsigned long ) altinsnptr + offset ;
/*
* If we ' re branching inside the alternate sequence ,
* do not rewrite the instruction , as it is already
* correct . Otherwise , generate the new instruction .
*/
if ( branch_insn_requires_update ( alt , target ) ) {
offset = target - ( unsigned long ) insnptr ;
insn = aarch64_set_branch_offset ( insn , offset ) ;
}
2016-09-09 16:07:13 +03:00
} else if ( aarch64_insn_is_adrp ( insn ) ) {
s32 orig_offset , new_offset ;
unsigned long target ;
/*
* If we ' re replacing an adrp instruction , which uses PC - relative
* immediate addressing , adjust the offset to reflect the new
* PC . adrp operates on 4 K aligned addresses .
*/
orig_offset = aarch64_insn_adrp_get_offset ( insn ) ;
target = align_down ( altinsnptr , SZ_4K ) + orig_offset ;
new_offset = target - align_down ( insnptr , SZ_4K ) ;
insn = aarch64_insn_adrp_set_offset ( insn , new_offset ) ;
2016-09-09 16:07:11 +03:00
} else if ( aarch64_insn_uses_literal ( insn ) ) {
/*
* Disallow patching unhandled instructions using PC relative
* literal addresses
*/
BUG ( ) ;
2015-06-01 12:47:40 +03:00
}
return insn ;
}
2017-12-03 15:02:14 +03:00
static void patch_alternative ( struct alt_instr * alt ,
__le32 * origptr , __le32 * updptr , int nr_inst )
{
__le32 * replptr ;
int i ;
replptr = ALT_REPL_PTR ( alt ) ;
for ( i = 0 ; i < nr_inst ; i + + ) {
u32 insn ;
insn = get_alt_insn ( alt , origptr + i , replptr + i ) ;
updptr [ i ] = cpu_to_le32 ( insn ) ;
}
}
2018-06-22 11:31:15 +03:00
/*
* We provide our own , private D - cache cleaning function so that we don ' t
* accidentally call into the cache . S code , which is patched by us at
* runtime .
*/
static void clean_dcache_range_nopatch ( u64 start , u64 end )
{
u64 cur , d_size , ctr_el0 ;
ctr_el0 = read_sanitised_ftr_reg ( SYS_CTR_EL0 ) ;
d_size = 4 < < cpuid_feature_extract_unsigned_field ( ctr_el0 ,
CTR_DMINLINE_SHIFT ) ;
cur = start & ~ ( d_size - 1 ) ;
do {
/*
* We must clean + invalidate to the PoC in order to avoid
* Cortex - A53 errata 826319 , 827319 , 824069 and 819472
* ( this corresponds to ARM64_WORKAROUND_CLEAN_CACHE )
*/
asm volatile ( " dc civac, %0 " : : " r " ( cur ) : " memory " ) ;
} while ( cur + = d_size , cur < end ) ;
}
static void __apply_alternatives ( void * alt_region , bool is_module )
2014-11-14 18:54:08 +03:00
{
struct alt_instr * alt ;
2014-11-28 16:40:45 +03:00
struct alt_region * region = alt_region ;
2017-12-03 15:02:14 +03:00
__le32 * origptr , * updptr ;
alternative_cb_t alt_cb ;
2014-11-14 18:54:08 +03:00
2014-11-28 16:40:45 +03:00
for ( alt = region - > begin ; alt < region - > end ; alt + + ) {
2017-12-03 15:02:14 +03:00
int nr_inst ;
2015-06-01 12:47:40 +03:00
2017-12-03 15:02:14 +03:00
/* Use ARM64_CB_PATCH as an unconditional patch */
if ( alt - > cpufeature < ARM64_CB_PATCH & &
! cpus_have_cap ( alt - > cpufeature ) )
2014-11-14 18:54:08 +03:00
continue ;
2017-12-03 15:02:14 +03:00
if ( alt - > cpufeature = = ARM64_CB_PATCH )
BUG_ON ( alt - > alt_len ! = 0 ) ;
else
BUG_ON ( alt - > alt_len ! = alt - > orig_len ) ;
2014-11-14 18:54:08 +03:00
pr_info_once ( " patching kernel code \n " ) ;
2015-06-01 12:47:40 +03:00
origptr = ALT_ORIG_PTR ( alt ) ;
2018-06-22 11:31:15 +03:00
updptr = is_module ? origptr : lm_alias ( origptr ) ;
2017-12-03 15:02:14 +03:00
nr_inst = alt - > orig_len / AARCH64_INSN_SIZE ;
2015-06-01 12:47:40 +03:00
2017-12-03 15:02:14 +03:00
if ( alt - > cpufeature < ARM64_CB_PATCH )
alt_cb = patch_alternative ;
else
alt_cb = ALT_REPL_PTR ( alt ) ;
alt_cb ( alt , origptr , updptr , nr_inst ) ;
2015-06-01 12:47:40 +03:00
2018-06-22 11:31:15 +03:00
if ( ! is_module ) {
clean_dcache_range_nopatch ( ( u64 ) origptr ,
( u64 ) ( origptr + nr_inst ) ) ;
}
}
/*
* The core module code takes care of cache maintenance in
* flush_module_icache ( ) .
*/
if ( ! is_module ) {
dsb ( ish ) ;
__flush_icache_all ( ) ;
isb ( ) ;
2014-11-14 18:54:08 +03:00
}
}
2015-07-28 21:07:28 +03:00
/*
* We might be patching the stop_machine state machine , so implement a
* really simple polling protocol here .
*/
static int __apply_alternatives_multi_stop ( void * unused )
2014-11-14 18:54:08 +03:00
{
2014-11-28 16:40:45 +03:00
struct alt_region region = {
2016-08-24 20:27:28 +03:00
. begin = ( struct alt_instr * ) __alt_instructions ,
. end = ( struct alt_instr * ) __alt_instructions_end ,
2014-11-28 16:40:45 +03:00
} ;
2015-07-28 21:07:28 +03:00
/* We always have a CPU 0 at this point (__init) */
if ( smp_processor_id ( ) ) {
2018-01-08 18:38:06 +03:00
while ( ! READ_ONCE ( alternatives_applied ) )
2015-07-28 21:07:28 +03:00
cpu_relax ( ) ;
2015-08-04 20:52:09 +03:00
isb ( ) ;
2015-07-28 21:07:28 +03:00
} else {
2018-01-08 18:38:06 +03:00
BUG_ON ( alternatives_applied ) ;
2018-06-22 11:31:15 +03:00
__apply_alternatives ( & region , false ) ;
2015-07-28 21:07:28 +03:00
/* Barriers provided by the cache flushing */
2018-01-08 18:38:06 +03:00
WRITE_ONCE ( alternatives_applied , 1 ) ;
2015-07-28 21:07:28 +03:00
}
return 0 ;
}
void __init apply_alternatives_all ( void )
{
2014-11-14 18:54:08 +03:00
/* better not try code patching on a live SMP system */
2015-07-28 21:07:28 +03:00
stop_machine ( __apply_alternatives_multi_stop , NULL , cpu_online_mask ) ;
2014-11-28 16:40:45 +03:00
}
2018-06-22 11:31:15 +03:00
# ifdef CONFIG_MODULES
void apply_alternatives_module ( void * start , size_t length )
2014-11-28 16:40:45 +03:00
{
struct alt_region region = {
. begin = start ,
. end = start + length ,
} ;
2018-06-22 11:31:15 +03:00
__apply_alternatives ( & region , true ) ;
2014-11-14 18:54:08 +03:00
}
2018-06-22 11:31:15 +03:00
# endif