2008-05-12 23:20:47 +04:00
/*
* trace stack traces
*
* Copyright ( C ) 2007 Steven Rostedt < srostedt @ redhat . com >
* Copyright ( C ) 2008 Ingo Molnar < mingo @ redhat . com >
2008-05-12 23:20:47 +04:00
* Copyright ( C ) 2004 , 2005 , Soeren Sandmann
2008-05-12 23:20:47 +04:00
*/
# include <linux/kallsyms.h>
2008-05-12 23:20:47 +04:00
# include <linux/debugfs.h>
# include <linux/hrtimer.h>
2008-05-12 23:20:47 +04:00
# include <linux/uaccess.h>
# include <linux/ftrace.h>
2008-05-12 23:20:47 +04:00
# include <linux/module.h>
2008-05-12 23:20:47 +04:00
# include <linux/irq.h>
2008-05-12 23:20:47 +04:00
# include <linux/fs.h>
2008-05-12 23:20:47 +04:00
# include "trace.h"
2008-05-12 23:20:47 +04:00
static struct trace_array * sysprof_trace ;
2008-05-12 23:20:47 +04:00
static int __read_mostly tracer_enabled ;
2008-05-12 23:20:47 +04:00
/*
* 10 msecs for now :
*/
2008-05-12 23:20:47 +04:00
static const unsigned long sample_period = 1000000 ;
2008-05-12 23:20:47 +04:00
static const unsigned int sample_max_depth = 512 ;
2008-05-12 23:20:47 +04:00
/*
* Per CPU hrtimers that do the profiling :
*/
static DEFINE_PER_CPU ( struct hrtimer , stack_trace_hrtimer ) ;
2008-05-12 23:20:47 +04:00
struct stack_frame {
const void __user * next_fp ;
unsigned long return_address ;
} ;
static int copy_stack_frame ( const void __user * fp , struct stack_frame * frame )
{
2008-05-12 23:20:48 +04:00
int ret ;
2008-05-12 23:20:47 +04:00
if ( ! access_ok ( VERIFY_READ , fp , sizeof ( * frame ) ) )
return 0 ;
2008-05-12 23:20:48 +04:00
ret = 1 ;
pagefault_disable ( ) ;
if ( __copy_from_user_inatomic ( frame , fp , sizeof ( * frame ) ) )
ret = 0 ;
pagefault_enable ( ) ;
2008-05-12 23:20:47 +04:00
2008-05-12 23:20:48 +04:00
return ret ;
2008-05-12 23:20:47 +04:00
}
static void timer_notify ( struct pt_regs * regs , int cpu )
{
struct trace_array_cpu * data ;
struct stack_frame frame ;
struct trace_array * tr ;
2008-05-12 23:20:48 +04:00
const void __user * fp ;
2008-05-12 23:20:47 +04:00
int is_user ;
int i ;
if ( ! regs )
return ;
tr = sysprof_trace ;
data = tr - > data [ cpu ] ;
is_user = user_mode ( regs ) ;
if ( ! current | | current - > pid = = 0 )
return ;
if ( is_user & & current - > state ! = TASK_RUNNING )
return ;
if ( ! is_user ) {
/* kernel */
ftrace ( tr , data , current - > pid , 1 , 0 ) ;
return ;
}
trace_special ( tr , data , 0 , current - > pid , regs - > ip ) ;
2008-05-12 23:20:48 +04:00
fp = ( void __user * ) regs - > bp ;
2008-05-12 23:20:47 +04:00
2008-05-12 23:20:47 +04:00
for ( i = 0 ; i < sample_max_depth ; i + + ) {
2008-05-12 23:20:48 +04:00
frame . next_fp = 0 ;
frame . return_address = 0 ;
if ( ! copy_stack_frame ( fp , & frame ) )
2008-05-12 23:20:47 +04:00
break ;
2008-05-12 23:20:48 +04:00
if ( ( unsigned long ) fp < regs - > sp )
2008-05-12 23:20:47 +04:00
break ;
trace_special ( tr , data , 1 , frame . return_address ,
2008-05-12 23:20:48 +04:00
( unsigned long ) fp ) ;
fp = frame . next_fp ;
2008-05-12 23:20:47 +04:00
}
trace_special ( tr , data , 2 , current - > pid , i ) ;
2008-05-12 23:20:48 +04:00
/*
* Special trace entry if we overflow the max depth :
*/
2008-05-12 23:20:47 +04:00
if ( i = = sample_max_depth )
2008-05-12 23:20:47 +04:00
trace_special ( tr , data , - 1 , - 1 , - 1 ) ;
}
2008-05-12 23:20:47 +04:00
static enum hrtimer_restart stack_trace_timer_fn ( struct hrtimer * hrtimer )
{
/* trace here */
2008-05-12 23:20:47 +04:00
timer_notify ( get_irq_regs ( ) , smp_processor_id ( ) ) ;
2008-05-12 23:20:47 +04:00
hrtimer_forward_now ( hrtimer , ns_to_ktime ( sample_period ) ) ;
return HRTIMER_RESTART ;
}
static void start_stack_timer ( int cpu )
{
struct hrtimer * hrtimer = & per_cpu ( stack_trace_hrtimer , cpu ) ;
hrtimer_init ( hrtimer , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
hrtimer - > function = stack_trace_timer_fn ;
hrtimer - > cb_mode = HRTIMER_CB_IRQSAFE_NO_SOFTIRQ ;
hrtimer_start ( hrtimer , ns_to_ktime ( sample_period ) , HRTIMER_MODE_REL ) ;
}
static void start_stack_timers ( void )
{
cpumask_t saved_mask = current - > cpus_allowed ;
int cpu ;
for_each_online_cpu ( cpu ) {
set_cpus_allowed_ptr ( current , & cpumask_of_cpu ( cpu ) ) ;
start_stack_timer ( cpu ) ;
}
set_cpus_allowed_ptr ( current , & saved_mask ) ;
}
static void stop_stack_timer ( int cpu )
{
struct hrtimer * hrtimer = & per_cpu ( stack_trace_hrtimer , cpu ) ;
hrtimer_cancel ( hrtimer ) ;
}
static void stop_stack_timers ( void )
{
int cpu ;
for_each_online_cpu ( cpu )
stop_stack_timer ( cpu ) ;
}
2008-05-12 23:20:47 +04:00
static notrace void stack_reset ( struct trace_array * tr )
{
int cpu ;
tr - > time_start = ftrace_now ( tr - > cpu ) ;
for_each_online_cpu ( cpu )
tracing_reset ( tr - > data [ cpu ] ) ;
}
static notrace void start_stack_trace ( struct trace_array * tr )
{
stack_reset ( tr ) ;
2008-05-12 23:20:47 +04:00
start_stack_timers ( ) ;
2008-05-12 23:20:47 +04:00
tracer_enabled = 1 ;
}
static notrace void stop_stack_trace ( struct trace_array * tr )
{
2008-05-12 23:20:47 +04:00
stop_stack_timers ( ) ;
2008-05-12 23:20:47 +04:00
tracer_enabled = 0 ;
}
static notrace void stack_trace_init ( struct trace_array * tr )
{
2008-05-12 23:20:47 +04:00
sysprof_trace = tr ;
2008-05-12 23:20:47 +04:00
if ( tr - > ctrl )
start_stack_trace ( tr ) ;
}
static notrace void stack_trace_reset ( struct trace_array * tr )
{
if ( tr - > ctrl )
stop_stack_trace ( tr ) ;
}
static void stack_trace_ctrl_update ( struct trace_array * tr )
{
/* When starting a new trace, reset the buffers */
if ( tr - > ctrl )
start_stack_trace ( tr ) ;
else
stop_stack_trace ( tr ) ;
}
static struct tracer stack_trace __read_mostly =
{
. name = " sysprof " ,
. init = stack_trace_init ,
. reset = stack_trace_reset ,
. ctrl_update = stack_trace_ctrl_update ,
# ifdef CONFIG_FTRACE_SELFTEST
2008-05-12 23:20:47 +04:00
. selftest = trace_selftest_startup_sysprof ,
2008-05-12 23:20:47 +04:00
# endif
} ;
__init static int init_stack_trace ( void )
{
return register_tracer ( & stack_trace ) ;
}
device_initcall ( init_stack_trace ) ;