2005-04-16 15:20:36 -07:00
/**
* @ file common . c
*
* @ remark Copyright 2004 Oprofile Authors
2010-04-30 11:36:54 +01:00
* @ remark Copyright 2010 ARM Ltd .
2005-04-16 15:20:36 -07:00
* @ remark Read the file COPYING
*
* @ author Zwane Mwaikambo
2010-04-30 11:36:54 +01:00
* @ author Will Deacon [ move to perf ]
2005-04-16 15:20:36 -07:00
*/
2010-04-30 11:36:54 +01:00
# include <linux/cpumask.h>
2010-04-30 11:38:39 +01:00
# include <linux/err.h>
2010-04-30 11:36:54 +01:00
# include <linux/errno.h>
2005-04-16 15:20:36 -07:00
# include <linux/init.h>
2010-04-30 11:36:54 +01:00
# include <linux/mutex.h>
2005-04-16 15:20:36 -07:00
# include <linux/oprofile.h>
2010-04-30 11:36:54 +01:00
# include <linux/perf_event.h>
2010-04-30 11:38:39 +01:00
# include <linux/platform_device.h>
2006-03-16 11:32:51 +00:00
# include <linux/slab.h>
2010-04-30 11:36:54 +01:00
# include <asm/stacktrace.h>
# include <linux/uaccess.h>
# include <asm/perf_event.h>
# include <asm/ptrace.h>
2005-04-16 15:20:36 -07:00
2010-04-30 11:36:54 +01:00
# ifdef CONFIG_HW_PERF_EVENTS
/*
* Per performance monitor configuration as set via oprofilefs .
*/
struct op_counter_config {
unsigned long count ;
unsigned long enabled ;
unsigned long event ;
unsigned long unit_mask ;
unsigned long kernel ;
unsigned long user ;
struct perf_event_attr attr ;
} ;
2005-04-16 15:20:36 -07:00
2005-10-28 14:54:21 +01:00
static int op_arm_enabled ;
2006-03-16 11:38:16 +00:00
static DEFINE_MUTEX ( op_arm_mutex ) ;
2005-04-16 15:20:36 -07:00
2010-04-30 11:36:54 +01:00
static struct op_counter_config * counter_config ;
static struct perf_event * * perf_events [ nr_cpumask_bits ] ;
static int perf_num_counters ;
/*
* Overflow callback for oprofile .
*/
static void op_overflow_handler ( struct perf_event * event , int unused ,
struct perf_sample_data * data , struct pt_regs * regs )
{
int id ;
u32 cpu = smp_processor_id ( ) ;
for ( id = 0 ; id < perf_num_counters ; + + id )
if ( perf_events [ cpu ] [ id ] = = event )
break ;
if ( id ! = perf_num_counters )
oprofile_add_sample ( regs , id ) ;
else
pr_warning ( " oprofile: ignoring spurious overflow "
" on cpu %u \n " , cpu ) ;
}
/*
* Called by op_arm_setup to create perf attributes to mirror the oprofile
* settings in counter_config . Attributes are created as ` pinned ' events and
* so are permanently scheduled on the PMU .
*/
static void op_perf_setup ( void )
{
int i ;
u32 size = sizeof ( struct perf_event_attr ) ;
struct perf_event_attr * attr ;
for ( i = 0 ; i < perf_num_counters ; + + i ) {
attr = & counter_config [ i ] . attr ;
memset ( attr , 0 , size ) ;
attr - > type = PERF_TYPE_RAW ;
attr - > size = size ;
attr - > config = counter_config [ i ] . event ;
attr - > sample_period = counter_config [ i ] . count ;
attr - > pinned = 1 ;
}
}
static int op_create_counter ( int cpu , int event )
{
int ret = 0 ;
struct perf_event * pevent ;
if ( ! counter_config [ event ] . enabled | | ( perf_events [ cpu ] [ event ] ! = NULL ) )
return ret ;
pevent = perf_event_create_kernel_counter ( & counter_config [ event ] . attr ,
cpu , - 1 ,
op_overflow_handler ) ;
if ( IS_ERR ( pevent ) ) {
ret = PTR_ERR ( pevent ) ;
} else if ( pevent - > state ! = PERF_EVENT_STATE_ACTIVE ) {
pr_warning ( " oprofile: failed to enable event %d "
" on CPU %d \n " , event , cpu ) ;
ret = - EBUSY ;
} else {
perf_events [ cpu ] [ event ] = pevent ;
}
return ret ;
}
static void op_destroy_counter ( int cpu , int event )
{
struct perf_event * pevent = perf_events [ cpu ] [ event ] ;
if ( pevent ) {
perf_event_release_kernel ( pevent ) ;
perf_events [ cpu ] [ event ] = NULL ;
}
}
/*
* Called by op_arm_start to create active perf events based on the
* perviously configured attributes .
*/
static int op_perf_start ( void )
{
int cpu , event , ret = 0 ;
for_each_online_cpu ( cpu ) {
for ( event = 0 ; event < perf_num_counters ; + + event ) {
ret = op_create_counter ( cpu , event ) ;
if ( ret )
goto out ;
}
}
out :
return ret ;
}
/*
* Called by op_arm_stop at the end of a profiling run .
*/
static void op_perf_stop ( void )
{
int cpu , event ;
for_each_online_cpu ( cpu )
for ( event = 0 ; event < perf_num_counters ; + + event )
op_destroy_counter ( cpu , event ) ;
}
static char * op_name_from_perf_id ( enum arm_perf_pmu_ids id )
{
switch ( id ) {
case ARM_PERF_PMU_ID_XSCALE1 :
return " arm/xscale1 " ;
case ARM_PERF_PMU_ID_XSCALE2 :
return " arm/xscale2 " ;
case ARM_PERF_PMU_ID_V6 :
return " arm/armv6 " ;
case ARM_PERF_PMU_ID_V6MP :
return " arm/mpcore " ;
case ARM_PERF_PMU_ID_CA8 :
return " arm/armv7 " ;
case ARM_PERF_PMU_ID_CA9 :
return " arm/armv7-ca9 " ;
default :
return NULL ;
}
}
2005-04-16 15:20:36 -07:00
2005-10-28 14:54:21 +01:00
static int op_arm_create_files ( struct super_block * sb , struct dentry * root )
2005-04-16 15:20:36 -07:00
{
unsigned int i ;
2010-04-30 11:36:54 +01:00
for ( i = 0 ; i < perf_num_counters ; i + + ) {
2005-04-16 15:20:36 -07:00
struct dentry * dir ;
2006-03-16 11:32:51 +00:00
char buf [ 4 ] ;
2005-04-16 15:20:36 -07:00
snprintf ( buf , sizeof buf , " %d " , i ) ;
dir = oprofilefs_mkdir ( sb , root , buf ) ;
oprofilefs_create_ulong ( sb , dir , " enabled " , & counter_config [ i ] . enabled ) ;
oprofilefs_create_ulong ( sb , dir , " event " , & counter_config [ i ] . event ) ;
oprofilefs_create_ulong ( sb , dir , " count " , & counter_config [ i ] . count ) ;
oprofilefs_create_ulong ( sb , dir , " unit_mask " , & counter_config [ i ] . unit_mask ) ;
oprofilefs_create_ulong ( sb , dir , " kernel " , & counter_config [ i ] . kernel ) ;
oprofilefs_create_ulong ( sb , dir , " user " , & counter_config [ i ] . user ) ;
}
return 0 ;
}
2005-10-28 14:54:21 +01:00
static int op_arm_setup ( void )
2005-04-16 15:20:36 -07:00
{
spin_lock ( & oprofilefs_lock ) ;
2010-04-30 11:36:54 +01:00
op_perf_setup ( ) ;
2005-04-16 15:20:36 -07:00
spin_unlock ( & oprofilefs_lock ) ;
2010-04-30 11:36:54 +01:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
2005-10-28 14:54:21 +01:00
static int op_arm_start ( void )
2005-04-16 15:20:36 -07:00
{
int ret = - EBUSY ;
2006-03-16 11:38:16 +00:00
mutex_lock ( & op_arm_mutex ) ;
2005-10-28 14:54:21 +01:00
if ( ! op_arm_enabled ) {
2010-04-30 11:36:54 +01:00
ret = 0 ;
op_perf_start ( ) ;
op_arm_enabled = 1 ;
2005-04-16 15:20:36 -07:00
}
2006-03-16 11:38:16 +00:00
mutex_unlock ( & op_arm_mutex ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
2005-10-28 14:54:21 +01:00
static void op_arm_stop ( void )
2005-04-16 15:20:36 -07:00
{
2006-03-16 11:38:16 +00:00
mutex_lock ( & op_arm_mutex ) ;
2005-10-28 14:54:21 +01:00
if ( op_arm_enabled )
2010-04-30 11:36:54 +01:00
op_perf_stop ( ) ;
2005-10-28 14:54:21 +01:00
op_arm_enabled = 0 ;
2006-03-16 11:38:16 +00:00
mutex_unlock ( & op_arm_mutex ) ;
2005-04-16 15:20:36 -07:00
}
2005-10-28 14:51:15 +01:00
# ifdef CONFIG_PM
2010-04-30 11:38:39 +01:00
static int op_arm_suspend ( struct platform_device * dev , pm_message_t state )
2005-10-28 14:51:15 +01:00
{
2006-03-16 11:38:16 +00:00
mutex_lock ( & op_arm_mutex ) ;
2005-10-28 14:54:21 +01:00
if ( op_arm_enabled )
2010-04-30 11:36:54 +01:00
op_perf_stop ( ) ;
2006-03-16 11:38:16 +00:00
mutex_unlock ( & op_arm_mutex ) ;
2005-10-28 14:51:15 +01:00
return 0 ;
}
2010-04-30 11:38:39 +01:00
static int op_arm_resume ( struct platform_device * dev )
2005-10-28 14:51:15 +01:00
{
2006-03-16 11:38:16 +00:00
mutex_lock ( & op_arm_mutex ) ;
2010-04-30 11:36:54 +01:00
if ( op_arm_enabled & & op_perf_start ( ) )
2005-10-28 14:54:21 +01:00
op_arm_enabled = 0 ;
2006-03-16 11:38:16 +00:00
mutex_unlock ( & op_arm_mutex ) ;
2005-10-28 14:51:15 +01:00
return 0 ;
}
2010-04-30 11:38:39 +01:00
static struct platform_driver oprofile_driver = {
. driver = {
. name = " arm-oprofile " ,
} ,
2005-10-28 14:54:21 +01:00
. resume = op_arm_resume ,
. suspend = op_arm_suspend ,
2005-10-28 14:51:15 +01:00
} ;
2010-04-30 11:38:39 +01:00
static struct platform_device * oprofile_pdev ;
2005-10-28 14:51:15 +01:00
static int __init init_driverfs ( void )
{
int ret ;
2010-04-30 11:38:39 +01:00
ret = platform_driver_register ( & oprofile_driver ) ;
if ( ret )
goto out ;
oprofile_pdev = platform_device_register_simple (
oprofile_driver . driver . name , 0 , NULL , 0 ) ;
if ( IS_ERR ( oprofile_pdev ) ) {
ret = PTR_ERR ( oprofile_pdev ) ;
platform_driver_unregister ( & oprofile_driver ) ;
}
2005-10-28 14:51:15 +01:00
2010-04-30 11:38:39 +01:00
out :
2005-10-28 14:51:15 +01:00
return ret ;
}
static void exit_driverfs ( void )
{
2010-04-30 11:38:39 +01:00
platform_device_unregister ( oprofile_pdev ) ;
platform_driver_unregister ( & oprofile_driver ) ;
2005-10-28 14:51:15 +01:00
}
# else
2010-04-30 11:38:39 +01:00
static int __init init_driverfs ( void ) { return 0 ; }
2005-10-28 14:51:15 +01:00
# define exit_driverfs() do { } while (0)
# endif /* CONFIG_PM */
2010-04-30 11:36:54 +01:00
static int report_trace ( struct stackframe * frame , void * d )
2005-04-16 15:20:36 -07:00
{
2010-04-30 11:36:54 +01:00
unsigned int * depth = d ;
2005-10-28 14:56:04 +01:00
2010-04-30 11:36:54 +01:00
if ( * depth ) {
oprofile_add_trace ( frame - > pc ) ;
( * depth ) - - ;
}
2007-02-27 12:09:33 +01:00
2010-04-30 11:36:54 +01:00
return * depth = = 0 ;
}
2005-10-28 14:56:04 +01:00
2010-04-30 11:36:54 +01:00
/*
* The registers we ' re interested in are at the end of the variable
* length saved register structure . The fp points at the end of this
* structure so the address of this struct is :
* ( struct frame_tail * ) ( xxx - > fp ) - 1
*/
struct frame_tail {
struct frame_tail * fp ;
unsigned long sp ;
unsigned long lr ;
} __attribute__ ( ( packed ) ) ;
2006-12-19 12:41:22 +00:00
2010-04-30 11:36:54 +01:00
static struct frame_tail * user_backtrace ( struct frame_tail * tail )
{
struct frame_tail buftail [ 2 ] ;
2006-12-19 14:17:46 +00:00
2010-04-30 11:36:54 +01:00
/* Also check accessibility of one struct frame_tail beyond */
if ( ! access_ok ( VERIFY_READ , tail , sizeof ( buftail ) ) )
return NULL ;
if ( __copy_from_user_inatomic ( buftail , tail , sizeof ( buftail ) ) )
return NULL ;
2008-08-12 19:07:39 +01:00
2010-04-30 11:36:54 +01:00
oprofile_add_trace ( buftail [ 0 ] . lr ) ;
2005-10-28 14:56:04 +01:00
2010-04-30 11:36:54 +01:00
/* frame pointers should strictly progress back up the stack
* ( towards higher addresses ) */
if ( tail > = buftail [ 0 ] . fp )
return NULL ;
return buftail [ 0 ] . fp - 1 ;
}
static void arm_backtrace ( struct pt_regs * const regs , unsigned int depth )
{
struct frame_tail * tail = ( ( struct frame_tail * ) regs - > ARM_fp ) - 1 ;
if ( ! user_mode ( regs ) ) {
struct stackframe frame ;
frame . fp = regs - > ARM_fp ;
frame . sp = regs - > ARM_sp ;
frame . lr = regs - > ARM_lr ;
frame . pc = regs - > ARM_pc ;
walk_stackframe ( & frame , report_trace , & depth ) ;
return ;
}
while ( depth - - & & tail & & ! ( ( unsigned long ) tail & 3 ) )
tail = user_backtrace ( tail ) ;
}
int __init oprofile_arch_init ( struct oprofile_operations * ops )
{
int cpu , ret = 0 ;
perf_num_counters = armpmu_get_max_events ( ) ;
counter_config = kcalloc ( perf_num_counters ,
sizeof ( struct op_counter_config ) , GFP_KERNEL ) ;
2006-03-16 11:32:51 +00:00
2010-04-30 11:36:54 +01:00
if ( ! counter_config ) {
pr_info ( " oprofile: failed to allocate %d "
" counters \n " , perf_num_counters ) ;
return - ENOMEM ;
2005-10-28 14:56:04 +01:00
}
2005-04-16 15:20:36 -07:00
2010-04-30 11:38:39 +01:00
ret = init_driverfs ( ) ;
if ( ret ) {
kfree ( counter_config ) ;
return ret ;
}
2010-04-30 11:36:54 +01:00
for_each_possible_cpu ( cpu ) {
perf_events [ cpu ] = kcalloc ( perf_num_counters ,
sizeof ( struct perf_event * ) , GFP_KERNEL ) ;
if ( ! perf_events [ cpu ] ) {
pr_info ( " oprofile: failed to allocate %d perf events "
" for cpu %d \n " , perf_num_counters , cpu ) ;
while ( - - cpu > = 0 )
kfree ( perf_events [ cpu ] ) ;
return - ENOMEM ;
}
}
ops - > backtrace = arm_backtrace ;
ops - > create_files = op_arm_create_files ;
ops - > setup = op_arm_setup ;
ops - > start = op_arm_start ;
ops - > stop = op_arm_stop ;
ops - > shutdown = op_arm_stop ;
ops - > cpu_type = op_name_from_perf_id ( armpmu_get_pmu_id ( ) ) ;
if ( ! ops - > cpu_type )
ret = - ENODEV ;
else
pr_info ( " oprofile: using %s \n " , ops - > cpu_type ) ;
2005-10-28 14:56:04 +01:00
return ret ;
2005-04-16 15:20:36 -07:00
}
2005-10-28 14:56:04 +01:00
void oprofile_arch_exit ( void )
2005-04-16 15:20:36 -07:00
{
2010-04-30 11:36:54 +01:00
int cpu , id ;
struct perf_event * event ;
if ( * perf_events ) {
2005-04-16 15:20:36 -07:00
exit_driverfs ( ) ;
2010-04-30 11:36:54 +01:00
for_each_possible_cpu ( cpu ) {
for ( id = 0 ; id < perf_num_counters ; + + id ) {
event = perf_events [ cpu ] [ id ] ;
if ( event ! = NULL )
perf_event_release_kernel ( event ) ;
}
kfree ( perf_events [ cpu ] ) ;
}
2005-04-16 15:20:36 -07:00
}
2010-04-30 11:36:54 +01:00
if ( counter_config )
kfree ( counter_config ) ;
}
# else
int __init oprofile_arch_init ( struct oprofile_operations * ops )
{
pr_info ( " oprofile: hardware counters not available \n " ) ;
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
2010-04-30 11:36:54 +01:00
void oprofile_arch_exit ( void ) { }
# endif /* CONFIG_HW_PERF_EVENTS */