blob: 7eed00bbd26df9fe2cae268e1ccca2cbe38122b4 [file] [log] [blame] [edit]
/*
* mcount and friends -- ftrace stuff
*
* Copyright (C) 2009-2010 Analog Devices Inc.
* Licensed under the GPL-2 or later.
*/
#include <linux/linkage.h>
#include <asm/ftrace.h>
.text
#ifdef CONFIG_DYNAMIC_FTRACE
/* Simple stub so we can boot the kernel until runtime patching has
* disabled all calls to this. Then it'll be unused.
*/
ENTRY(__mcount)
# if ANOMALY_05000371
nop; nop; nop; nop;
# endif
rts;
ENDPROC(__mcount)
/* GCC will have called us before setting up the function prologue, so we
* can clobber the normal scratch registers, but we need to make sure to
* save/restore the registers used for argument passing (R0-R2) in case
* the profiled function is using them. With data registers, R3 is the
* only one we can blow away. With pointer registers, we have P0-P2.
*
* Upon entry, the RETS will point to the top of the current profiled
* function. And since GCC pushed the previous RETS for us, the previous
* function will be waiting there. mmmm pie.
*/
ENTRY(_ftrace_caller)
# ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
/* optional micro optimization: return if stopped */
p1.l = _function_trace_stop;
p1.h = _function_trace_stop;
r3 = [p1];
cc = r3 == 0;
if ! cc jump _ftrace_stub (bp);
# endif
/* save first/second/third function arg and the return register */
[--sp] = r2;
[--sp] = r0;
[--sp] = r1;
[--sp] = rets;
/* function_trace_call(unsigned long ip, unsigned long parent_ip):
* ip: this point was called by ...
* parent_ip: ... this function
* the ip itself will need adjusting for the mcount call
*/
r0 = rets;
r1 = [sp + 16]; /* skip the 4 local regs on stack */
r0 += -MCOUNT_INSN_SIZE;
.globl _ftrace_call
_ftrace_call:
call _ftrace_stub
# ifdef CONFIG_FUNCTION_GRAPH_TRACER
.globl _ftrace_graph_call
_ftrace_graph_call:
nop; /* jump _ftrace_graph_caller; */
# endif
/* restore state and get out of dodge */
.Lfinish_trace:
rets = [sp++];
r1 = [sp++];
r0 = [sp++];
r2 = [sp++];
.globl _ftrace_stub
_ftrace_stub:
rts;
ENDPROC(_ftrace_caller)
#else
/* See documentation for _ftrace_caller */
ENTRY(__mcount)
# ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
/* optional micro optimization: return if stopped */
p1.l = _function_trace_stop;
p1.h = _function_trace_stop;
r3 = [p1];
cc = r3 == 0;
if ! cc jump _ftrace_stub (bp);
# endif
/* save third function arg early so we can do testing below */
[--sp] = r2;
/* load the function pointer to the tracer */
p0.l = _ftrace_trace_function;
p0.h = _ftrace_trace_function;
r3 = [p0];
/* optional micro optimization: don't call the stub tracer */
r2.l = _ftrace_stub;
r2.h = _ftrace_stub;
cc = r2 == r3;
if ! cc jump .Ldo_trace;
# ifdef CONFIG_FUNCTION_GRAPH_TRACER
/* if the ftrace_graph_return function pointer is not set to
* the ftrace_stub entry, call prepare_ftrace_return().
*/
p0.l = _ftrace_graph_return;
p0.h = _ftrace_graph_return;
r3 = [p0];
cc = r2 == r3;
if ! cc jump _ftrace_graph_caller;
/* similarly, if the ftrace_graph_entry function pointer is not
* set to the ftrace_graph_entry_stub entry, ...
*/
p0.l = _ftrace_graph_entry;
p0.h = _ftrace_graph_entry;
r2.l = _ftrace_graph_entry_stub;
r2.h = _ftrace_graph_entry_stub;
r3 = [p0];
cc = r2 == r3;
if ! cc jump _ftrace_graph_caller;
# endif
r2 = [sp++];
rts;
.Ldo_trace:
/* save first/second function arg and the return register */
[--sp] = r0;
[--sp] = r1;
[--sp] = rets;
/* setup the tracer function */
p0 = r3;
/* function_trace_call(unsigned long ip, unsigned long parent_ip):
* ip: this point was called by ...
* parent_ip: ... this function
* the ip itself will need adjusting for the mcount call
*/
r0 = rets;
r1 = [sp + 16]; /* skip the 4 local regs on stack */
r0 += -MCOUNT_INSN_SIZE;
/* call the tracer */
call (p0);
/* restore state and get out of dodge */
.Lfinish_trace:
rets = [sp++];
r1 = [sp++];
r0 = [sp++];
r2 = [sp++];
.globl _ftrace_stub
_ftrace_stub:
rts;
ENDPROC(__mcount)
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
/* The prepare_ftrace_return() function is similar to the trace function
* except it takes a pointer to the location of the frompc. This is so
* the prepare_ftrace_return() can hijack it temporarily for probing
* purposes.
*/
ENTRY(_ftrace_graph_caller)
# ifndef CONFIG_DYNAMIC_FTRACE
/* save first/second function arg and the return register */
[--sp] = r0;
[--sp] = r1;
[--sp] = rets;
/* prepare_ftrace_return(parent, self_addr, frame_pointer) */
r0 = sp; /* unsigned long *parent */
r1 = rets; /* unsigned long self_addr */
# else
r0 = sp; /* unsigned long *parent */
r1 = [sp]; /* unsigned long self_addr */
# endif
# ifdef CONFIG_HAVE_FUNCTION_GRAPH_FP_TEST
r2 = fp; /* unsigned long frame_pointer */
# endif
r0 += 16; /* skip the 4 local regs on stack */
r1 += -MCOUNT_INSN_SIZE;
call _prepare_ftrace_return;
jump .Lfinish_trace;
ENDPROC(_ftrace_graph_caller)
/* Undo the rewrite caused by ftrace_graph_caller(). The common function
* ftrace_return_to_handler() will return the original rets so we can
* restore it and be on our way.
*/
ENTRY(_return_to_handler)
/* make sure original return values are saved */
[--sp] = p0;
[--sp] = r0;
[--sp] = r1;
/* get original return address */
# ifdef CONFIG_HAVE_FUNCTION_GRAPH_FP_TEST
r0 = fp; /* Blackfin is sane, so omit this */
# endif
call _ftrace_return_to_handler;
rets = r0;
/* anomaly 05000371 - make sure we have at least three instructions
* between rets setting and the return
*/
r1 = [sp++];
r0 = [sp++];
p0 = [sp++];
rts;
ENDPROC(_return_to_handler)
#endif