2008-05-31 14:23:50 +05:30
/*
* Dynamic function tracing support .
*
* Copyright ( C ) 2008 Abhishek Sagar < sagar . abhishek @ gmail . com >
2010-08-10 19:43:28 +01:00
* Copyright ( C ) 2010 Rabin Vincent < rabin @ rab . in >
2008-05-31 14:23:50 +05:30
*
* For licencing details , see COPYING .
*
* Defines low - level handling of mcount calls when the kernel
* is compiled with the - pg flag . When using dynamic ftrace , the
2010-08-10 19:43:28 +01:00
* mcount call - sites get patched with NOP till they are enabled .
* All code mutation routines here are called under stop_machine ( ) .
2008-05-31 14:23:50 +05:30
*/
# include <linux/ftrace.h>
2010-08-10 19:43:28 +01:00
# include <linux/uaccess.h>
2008-06-21 23:47:27 +05:30
2008-05-31 14:23:50 +05:30
# include <asm/cacheflush.h>
2008-06-21 23:47:27 +05:30
# include <asm/ftrace.h>
2008-05-31 14:23:50 +05:30
2010-08-10 19:52:35 +01:00
# ifdef CONFIG_THUMB2_KERNEL
# define NOP 0xeb04f85d /* pop.w {lr} */
# else
2010-08-10 19:43:28 +01:00
# define NOP 0xe8bd4000 /* pop {lr} */
2010-08-10 19:52:35 +01:00
# endif
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
# ifdef CONFIG_OLD_MCOUNT
# define OLD_MCOUNT_ADDR ((unsigned long) mcount)
# define OLD_FTRACE_ADDR ((unsigned long) ftrace_caller_old)
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
# define OLD_NOP 0xe1a00000 /* mov r0, r0 */
static unsigned long ftrace_nop_replace ( struct dyn_ftrace * rec )
{
return rec - > arch . old_mcount ? OLD_NOP : NOP ;
}
static unsigned long adjust_address ( struct dyn_ftrace * rec , unsigned long addr )
{
if ( ! rec - > arch . old_mcount )
return addr ;
if ( addr = = MCOUNT_ADDR )
addr = OLD_MCOUNT_ADDR ;
else if ( addr = = FTRACE_ADDR )
addr = OLD_FTRACE_ADDR ;
return addr ;
}
# else
static unsigned long ftrace_nop_replace ( struct dyn_ftrace * rec )
{
return NOP ;
}
static unsigned long adjust_address ( struct dyn_ftrace * rec , unsigned long addr )
2008-05-31 14:23:50 +05:30
{
2010-08-10 19:43:28 +01:00
return addr ;
2008-05-31 14:23:50 +05:30
}
2010-08-10 19:43:28 +01:00
# endif
2008-05-31 14:23:50 +05:30
/* construct a branch (BL) instruction to addr */
2010-08-10 19:52:35 +01:00
# ifdef CONFIG_THUMB2_KERNEL
static unsigned long ftrace_call_replace ( unsigned long pc , unsigned long addr )
{
unsigned long s , j1 , j2 , i1 , i2 , imm10 , imm11 ;
unsigned long first , second ;
long offset ;
offset = ( long ) addr - ( long ) ( pc + 4 ) ;
if ( offset < - 16777216 | | offset > 16777214 ) {
WARN_ON_ONCE ( 1 ) ;
return 0 ;
}
s = ( offset > > 24 ) & 0x1 ;
i1 = ( offset > > 23 ) & 0x1 ;
i2 = ( offset > > 22 ) & 0x1 ;
imm10 = ( offset > > 12 ) & 0x3ff ;
imm11 = ( offset > > 1 ) & 0x7ff ;
j1 = ( ! i1 ) ^ s ;
j2 = ( ! i2 ) ^ s ;
first = 0xf000 | ( s < < 10 ) | imm10 ;
second = 0xd000 | ( j1 < < 13 ) | ( j2 < < 11 ) | imm11 ;
return ( second < < 16 ) | first ;
}
# else
2010-08-10 19:43:28 +01:00
static unsigned long ftrace_call_replace ( unsigned long pc , unsigned long addr )
2008-05-31 14:23:50 +05:30
{
long offset ;
2010-08-10 19:43:28 +01:00
offset = ( long ) addr - ( long ) ( pc + 8 ) ;
2008-05-31 14:23:50 +05:30
if ( unlikely ( offset < - 33554432 | | offset > 33554428 ) ) {
/* Can't generate branches that far (from ARM ARM). Ftrace
2008-06-21 23:47:27 +05:30
* doesn ' t generate branches outside of kernel text .
2008-05-31 14:23:50 +05:30
*/
WARN_ON_ONCE ( 1 ) ;
2010-08-10 19:43:28 +01:00
return 0 ;
2008-05-31 14:23:50 +05:30
}
2010-08-10 19:43:28 +01:00
offset = ( offset > > 2 ) & 0x00ffffff ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
return 0xeb000000 | offset ;
}
2010-08-10 19:52:35 +01:00
# endif
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
static int ftrace_modify_code ( unsigned long pc , unsigned long old ,
unsigned long new )
{
unsigned long replaced ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
if ( probe_kernel_read ( & replaced , ( void * ) pc , MCOUNT_INSN_SIZE ) )
return - EFAULT ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
if ( replaced ! = old )
return - EINVAL ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
if ( probe_kernel_write ( ( void * ) pc , & new , MCOUNT_INSN_SIZE ) )
return - EPERM ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
flush_icache_range ( pc , pc + MCOUNT_INSN_SIZE ) ;
2008-05-31 14:23:50 +05:30
2010-08-10 19:43:28 +01:00
return 0 ;
2008-05-31 14:23:50 +05:30
}
int ftrace_update_ftrace_func ( ftrace_func_t func )
{
unsigned long pc , old ;
2010-08-10 19:43:28 +01:00
unsigned long new ;
int ret ;
2008-05-31 14:23:50 +05:30
pc = ( unsigned long ) & ftrace_call ;
2008-06-21 23:47:27 +05:30
memcpy ( & old , & ftrace_call , MCOUNT_INSN_SIZE ) ;
2008-05-31 14:23:50 +05:30
new = ftrace_call_replace ( pc , ( unsigned long ) func ) ;
2010-08-10 19:43:28 +01:00
ret = ftrace_modify_code ( pc , old , new ) ;
# ifdef CONFIG_OLD_MCOUNT
if ( ! ret ) {
pc = ( unsigned long ) & ftrace_call_old ;
memcpy ( & old , & ftrace_call_old , MCOUNT_INSN_SIZE ) ;
new = ftrace_call_replace ( pc , ( unsigned long ) func ) ;
ret = ftrace_modify_code ( pc , old , new ) ;
}
# endif
return ret ;
}
int ftrace_make_call ( struct dyn_ftrace * rec , unsigned long addr )
{
unsigned long new , old ;
unsigned long ip = rec - > ip ;
old = ftrace_nop_replace ( rec ) ;
new = ftrace_call_replace ( ip , adjust_address ( rec , addr ) ) ;
return ftrace_modify_code ( rec - > ip , old , new ) ;
}
int ftrace_make_nop ( struct module * mod ,
struct dyn_ftrace * rec , unsigned long addr )
{
unsigned long ip = rec - > ip ;
unsigned long old ;
unsigned long new ;
int ret ;
old = ftrace_call_replace ( ip , adjust_address ( rec , addr ) ) ;
new = ftrace_nop_replace ( rec ) ;
ret = ftrace_modify_code ( ip , old , new ) ;
# ifdef CONFIG_OLD_MCOUNT
if ( ret = = - EINVAL & & addr = = MCOUNT_ADDR ) {
rec - > arch . old_mcount = true ;
old = ftrace_call_replace ( ip , adjust_address ( rec , addr ) ) ;
new = ftrace_nop_replace ( rec ) ;
ret = ftrace_modify_code ( ip , old , new ) ;
}
# endif
2008-05-31 14:23:50 +05:30
return ret ;
}
int __init ftrace_dyn_arch_init ( void * data )
{
2010-08-10 19:43:28 +01:00
* ( unsigned long * ) data = 0 ;
2008-05-31 14:23:50 +05:30
return 0 ;
}