mirror of
https://github.com/torvalds/linux.git
synced 2024-12-05 02:23:16 +00:00
8fd6e5a8c8
Impact: update to PowerPC ftrace arch API This patch converts PowerPC to use the new dynamic ftrace arch API. Thanks to Paul Mackennas for pointing out the mistakes of my original test_24bit_addr function. Signed-off-by: Steven Rostedt <srostedt@redhat.com>
195 lines
4.1 KiB
C
195 lines
4.1 KiB
C
/*
|
|
* Code for replacing ftrace calls with jumps.
|
|
*
|
|
* Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
* Thanks goes out to P.A. Semi, Inc for supplying me with a PPC64 box.
|
|
*
|
|
*/
|
|
|
|
#include <linux/spinlock.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/ftrace.h>
|
|
|
|
|
|
static unsigned int ftrace_nop = 0x60000000;
|
|
|
|
#ifdef CONFIG_PPC32
|
|
# define GET_ADDR(addr) addr
|
|
#else
|
|
/* PowerPC64's functions are data that points to the functions */
|
|
# define GET_ADDR(addr) *(unsigned long *)addr
|
|
#endif
|
|
|
|
|
|
static unsigned int ftrace_calc_offset(long ip, long addr)
|
|
{
|
|
return (int)(addr - ip);
|
|
}
|
|
|
|
static unsigned char *ftrace_nop_replace(void)
|
|
{
|
|
return (char *)&ftrace_nop;
|
|
}
|
|
|
|
static unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr)
|
|
{
|
|
static unsigned int op;
|
|
|
|
/*
|
|
* It would be nice to just use create_function_call, but that will
|
|
* update the code itself. Here we need to just return the
|
|
* instruction that is going to be modified, without modifying the
|
|
* code.
|
|
*/
|
|
addr = GET_ADDR(addr);
|
|
|
|
/* Set to "bl addr" */
|
|
op = 0x48000001 | (ftrace_calc_offset(ip, addr) & 0x03fffffc);
|
|
|
|
/*
|
|
* No locking needed, this must be called via kstop_machine
|
|
* which in essence is like running on a uniprocessor machine.
|
|
*/
|
|
return (unsigned char *)&op;
|
|
}
|
|
|
|
#ifdef CONFIG_PPC64
|
|
# define _ASM_ALIGN " .align 3 "
|
|
# define _ASM_PTR " .llong "
|
|
#else
|
|
# define _ASM_ALIGN " .align 2 "
|
|
# define _ASM_PTR " .long "
|
|
#endif
|
|
|
|
static int
|
|
ftrace_modify_code(unsigned long ip, unsigned char *old_code,
|
|
unsigned char *new_code)
|
|
{
|
|
unsigned replaced;
|
|
unsigned old = *(unsigned *)old_code;
|
|
unsigned new = *(unsigned *)new_code;
|
|
int faulted = 0;
|
|
|
|
/*
|
|
* Note: Due to modules and __init, code can
|
|
* disappear and change, we need to protect against faulting
|
|
* as well as code changing.
|
|
*
|
|
* No real locking needed, this code is run through
|
|
* kstop_machine.
|
|
*/
|
|
asm volatile (
|
|
"1: lwz %1, 0(%2)\n"
|
|
" cmpw %1, %5\n"
|
|
" bne 2f\n"
|
|
" stwu %3, 0(%2)\n"
|
|
"2:\n"
|
|
".section .fixup, \"ax\"\n"
|
|
"3: li %0, 1\n"
|
|
" b 2b\n"
|
|
".previous\n"
|
|
".section __ex_table,\"a\"\n"
|
|
_ASM_ALIGN "\n"
|
|
_ASM_PTR "1b, 3b\n"
|
|
".previous"
|
|
: "=r"(faulted), "=r"(replaced)
|
|
: "r"(ip), "r"(new),
|
|
"0"(faulted), "r"(old)
|
|
: "memory");
|
|
|
|
if (replaced != old && replaced != new)
|
|
faulted = 2;
|
|
|
|
if (!faulted)
|
|
flush_icache_range(ip, ip + 8);
|
|
|
|
return faulted;
|
|
}
|
|
|
|
static int test_24bit_addr(unsigned long ip, unsigned long addr)
|
|
{
|
|
long diff;
|
|
|
|
/*
|
|
* Can we get to addr from ip in 24 bits?
|
|
* (26 really, since we mulitply by 4 for 4 byte alignment)
|
|
*/
|
|
diff = addr - ip;
|
|
|
|
/*
|
|
* Return true if diff is less than 1 << 25
|
|
* and greater than -1 << 26.
|
|
*/
|
|
return (diff < (1 << 25)) && (diff > (-1 << 26));
|
|
}
|
|
|
|
int ftrace_make_nop(struct module *mod,
|
|
struct dyn_ftrace *rec, unsigned long addr)
|
|
{
|
|
unsigned char *old, *new;
|
|
|
|
/*
|
|
* If the calling address is more that 24 bits away,
|
|
* then we had to use a trampoline to make the call.
|
|
* Otherwise just update the call site.
|
|
*/
|
|
if (test_24bit_addr(rec->ip, addr)) {
|
|
/* within range */
|
|
old = ftrace_call_replace(rec->ip, addr);
|
|
new = ftrace_nop_replace();
|
|
return ftrace_modify_code(rec->ip, old, new);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
|
|
{
|
|
unsigned char *old, *new;
|
|
|
|
/*
|
|
* If the calling address is more that 24 bits away,
|
|
* then we had to use a trampoline to make the call.
|
|
* Otherwise just update the call site.
|
|
*/
|
|
if (test_24bit_addr(rec->ip, addr)) {
|
|
/* within range */
|
|
old = ftrace_nop_replace();
|
|
new = ftrace_call_replace(rec->ip, addr);
|
|
return ftrace_modify_code(rec->ip, old, new);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ftrace_update_ftrace_func(ftrace_func_t func)
|
|
{
|
|
unsigned long ip = (unsigned long)(&ftrace_call);
|
|
unsigned char old[MCOUNT_INSN_SIZE], *new;
|
|
int ret;
|
|
|
|
memcpy(old, &ftrace_call, MCOUNT_INSN_SIZE);
|
|
new = ftrace_call_replace(ip, (unsigned long)func);
|
|
ret = ftrace_modify_code(ip, old, new);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __init ftrace_dyn_arch_init(void *data)
|
|
{
|
|
/* caller expects data to be zero */
|
|
unsigned long *p = data;
|
|
|
|
*p = 0;
|
|
|
|
return 0;
|
|
}
|
|
|