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>
2014-11-14 18:54:08 +03:00
# include <linux/stop_machine.h>
2015-06-01 12:47:40 +03:00
# define __ALT_PTR(a,f) (u32 *)((void *)&(a)->f + (a)->f)
# define ALT_ORIG_PTR(a) __ALT_PTR(a, orig_offset)
# define ALT_REPL_PTR(a) __ALT_PTR(a, alt_offset)
2014-11-14 18:54:08 +03:00
extern struct alt_instr __alt_instructions [ ] , __alt_instructions_end [ ] ;
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 ) )
return 1 ;
replptr = ( unsigned long ) ALT_REPL_PTR ( alt ) ;
if ( pc > = replptr & & pc < = ( replptr + alt - > alt_len ) )
return 0 ;
/*
* Branching into * another * alternate sequence is doomed , and
* we ' re not even trying to fix it up .
*/
BUG ( ) ;
}
static u32 get_alt_insn ( struct alt_instr * alt , u32 * insnptr , u32 * altinsnptr )
{
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 ) ;
}
}
return insn ;
}
2015-07-28 21:07:28 +03:00
static void __apply_alternatives ( void * alt_region )
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 ;
2015-06-01 12:47:40 +03:00
u32 * origptr , * replptr ;
2014-11-14 18:54:08 +03:00
2014-11-28 16:40:45 +03:00
for ( alt = region - > begin ; alt < region - > end ; alt + + ) {
2015-06-01 12:47:40 +03:00
u32 insn ;
int i , nr_inst ;
2014-11-14 18:54:08 +03:00
if ( ! cpus_have_cap ( alt - > cpufeature ) )
continue ;
2015-03-27 16:09:22 +03:00
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 ) ;
replptr = ALT_REPL_PTR ( alt ) ;
nr_inst = alt - > alt_len / sizeof ( insn ) ;
for ( i = 0 ; i < nr_inst ; i + + ) {
insn = get_alt_insn ( alt , origptr + i , replptr + i ) ;
* ( origptr + i ) = cpu_to_le32 ( insn ) ;
}
2014-11-14 18:54:08 +03:00
flush_icache_range ( ( uintptr_t ) origptr ,
2015-06-01 12:47:40 +03:00
( uintptr_t ) ( origptr + nr_inst ) ) ;
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
{
2015-07-28 21:07:28 +03:00
static int patched = 0 ;
2014-11-28 16:40:45 +03:00
struct alt_region region = {
. begin = __alt_instructions ,
. end = __alt_instructions_end ,
} ;
2015-07-28 21:07:28 +03:00
/* We always have a CPU 0 at this point (__init) */
if ( smp_processor_id ( ) ) {
while ( ! READ_ONCE ( patched ) )
cpu_relax ( ) ;
2015-08-04 20:52:09 +03:00
isb ( ) ;
2015-07-28 21:07:28 +03:00
} else {
BUG_ON ( patched ) ;
__apply_alternatives ( & region ) ;
/* Barriers provided by the cache flushing */
WRITE_ONCE ( patched , 1 ) ;
}
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
}
void apply_alternatives ( void * start , size_t length )
{
struct alt_region region = {
. begin = start ,
. end = start + length ,
} ;
__apply_alternatives ( & region ) ;
2014-11-14 18:54:08 +03:00
}