2012-02-18 17:50:51 +01:00
# include <linux/kernel.h>
2014-04-24 23:28:57 +02:00
# include <linux/spinlock.h>
2012-02-18 17:50:51 +01:00
# include <linux/kprobes.h>
2014-04-24 23:28:57 +02:00
# include <linux/mm.h>
2012-02-18 17:50:51 +01:00
# include <linux/stop_machine.h>
# include <asm/cacheflush.h>
2014-04-24 23:28:57 +02:00
# include <asm/fixmap.h>
2012-02-18 17:50:51 +01:00
# include <asm/smp_plat.h>
# include <asm/opcodes.h>
2015-01-09 10:19:49 +08:00
# include <asm/patch.h>
2012-02-18 17:50:51 +01:00
struct patch {
void * addr ;
unsigned int insn ;
} ;
2014-04-24 23:28:57 +02:00
static DEFINE_SPINLOCK ( patch_lock ) ;
static void __kprobes * patch_map ( void * addr , int fixmap , unsigned long * flags )
__acquires ( & patch_lock )
{
unsigned int uintaddr = ( uintptr_t ) addr ;
bool module = ! core_kernel_text ( uintaddr ) ;
struct page * page ;
if ( module & & IS_ENABLED ( CONFIG_DEBUG_SET_MODULE_RONX ) )
page = vmalloc_to_page ( addr ) ;
else if ( ! module & & IS_ENABLED ( CONFIG_DEBUG_RODATA ) )
page = virt_to_page ( addr ) ;
else
return addr ;
if ( flags )
spin_lock_irqsave ( & patch_lock , * flags ) ;
else
__acquire ( & patch_lock ) ;
set_fixmap ( fixmap , page_to_phys ( page ) ) ;
return ( void * ) ( __fix_to_virt ( fixmap ) + ( uintaddr & ~ PAGE_MASK ) ) ;
}
static void __kprobes patch_unmap ( int fixmap , unsigned long * flags )
__releases ( & patch_lock )
{
clear_fixmap ( fixmap ) ;
if ( flags )
spin_unlock_irqrestore ( & patch_lock , * flags ) ;
else
__release ( & patch_lock ) ;
}
void __kprobes __patch_text_real ( void * addr , unsigned int insn , bool remap )
2012-02-18 17:50:51 +01:00
{
bool thumb2 = IS_ENABLED ( CONFIG_THUMB2_KERNEL ) ;
2014-04-24 23:28:57 +02:00
unsigned int uintaddr = ( uintptr_t ) addr ;
bool twopage = false ;
unsigned long flags ;
void * waddr = addr ;
2012-02-18 17:50:51 +01:00
int size ;
2014-04-24 23:28:57 +02:00
if ( remap )
waddr = patch_map ( addr , FIX_TEXT_POKE0 , & flags ) ;
else
__acquire ( & patch_lock ) ;
2012-02-18 17:50:51 +01:00
if ( thumb2 & & __opcode_is_thumb16 ( insn ) ) {
2014-04-24 23:28:57 +02:00
* ( u16 * ) waddr = __opcode_to_mem_thumb16 ( insn ) ;
2012-02-18 17:50:51 +01:00
size = sizeof ( u16 ) ;
2014-04-24 23:28:57 +02:00
} else if ( thumb2 & & ( uintaddr & 2 ) ) {
2012-02-18 17:50:51 +01:00
u16 first = __opcode_thumb32_first ( insn ) ;
u16 second = __opcode_thumb32_second ( insn ) ;
2014-04-24 23:28:57 +02:00
u16 * addrh0 = waddr ;
u16 * addrh1 = waddr + 2 ;
twopage = ( uintaddr & ~ PAGE_MASK ) = = PAGE_SIZE - 2 ;
if ( twopage & & remap )
addrh1 = patch_map ( addr + 2 , FIX_TEXT_POKE1 , NULL ) ;
* addrh0 = __opcode_to_mem_thumb16 ( first ) ;
* addrh1 = __opcode_to_mem_thumb16 ( second ) ;
2012-02-18 17:50:51 +01:00
2014-04-24 23:28:57 +02:00
if ( twopage & & addrh1 ! = addr + 2 ) {
flush_kernel_vmap_range ( addrh1 , 2 ) ;
patch_unmap ( FIX_TEXT_POKE1 , NULL ) ;
}
2012-02-18 17:50:51 +01:00
size = sizeof ( u32 ) ;
} else {
if ( thumb2 )
insn = __opcode_to_mem_thumb32 ( insn ) ;
else
insn = __opcode_to_mem_arm ( insn ) ;
2014-04-24 23:28:57 +02:00
* ( u32 * ) waddr = insn ;
2012-02-18 17:50:51 +01:00
size = sizeof ( u32 ) ;
}
2014-04-24 23:28:57 +02:00
if ( waddr ! = addr ) {
flush_kernel_vmap_range ( waddr , twopage ? size / 2 : size ) ;
patch_unmap ( FIX_TEXT_POKE0 , & flags ) ;
} else
__release ( & patch_lock ) ;
2012-02-18 17:50:51 +01:00
flush_icache_range ( ( uintptr_t ) ( addr ) ,
( uintptr_t ) ( addr ) + size ) ;
}
static int __kprobes patch_text_stop_machine ( void * data )
{
struct patch * patch = data ;
__patch_text ( patch - > addr , patch - > insn ) ;
return 0 ;
}
void __kprobes patch_text ( void * addr , unsigned int insn )
{
struct patch patch = {
. addr = addr ,
. insn = insn ,
} ;
2014-04-24 23:28:57 +02:00
stop_machine ( patch_text_stop_machine , & patch , NULL ) ;
2012-02-18 17:50:51 +01:00
}