2010-02-25 18:09:45 -06:00
/*
* Performance event support - Freescale Embedded Performance Monitor
*
* Copyright 2008 - 2009 Paul Mackerras , IBM Corporation .
* Copyright 2010 Freescale Semiconductor , Inc .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/perf_event.h>
# include <linux/percpu.h>
# include <linux/hardirq.h>
# include <asm/reg_fsl_emb.h>
# include <asm/pmc.h>
# include <asm/machdep.h>
# include <asm/firmware.h>
# include <asm/ptrace.h>
struct cpu_hw_events {
int n_events ;
int disabled ;
u8 pmcs_enabled ;
struct perf_event * event [ MAX_HWEVENTS ] ;
} ;
static DEFINE_PER_CPU ( struct cpu_hw_events , cpu_hw_events ) ;
static struct fsl_emb_pmu * ppmu ;
/* Number of perf_events counting hardware events */
static atomic_t num_events ;
/* Used to avoid races in calling reserve/release_pmc_hardware */
static DEFINE_MUTEX ( pmc_reserve_mutex ) ;
/*
* If interrupts were soft - disabled when a PMU interrupt occurs , treat
* it as an NMI .
*/
static inline int perf_intr_is_nmi ( struct pt_regs * regs )
{
# ifdef __powerpc64__
return ! regs - > softe ;
# else
return 0 ;
# endif
}
static void perf_event_interrupt ( struct pt_regs * regs ) ;
/*
* Read one performance monitor counter ( PMC ) .
*/
static unsigned long read_pmc ( int idx )
{
unsigned long val ;
switch ( idx ) {
case 0 :
val = mfpmr ( PMRN_PMC0 ) ;
break ;
case 1 :
val = mfpmr ( PMRN_PMC1 ) ;
break ;
case 2 :
val = mfpmr ( PMRN_PMC2 ) ;
break ;
case 3 :
val = mfpmr ( PMRN_PMC3 ) ;
break ;
default :
printk ( KERN_ERR " oops trying to read PMC%d \n " , idx ) ;
val = 0 ;
}
return val ;
}
/*
* Write one PMC .
*/
static void write_pmc ( int idx , unsigned long val )
{
switch ( idx ) {
case 0 :
mtpmr ( PMRN_PMC0 , val ) ;
break ;
case 1 :
mtpmr ( PMRN_PMC1 , val ) ;
break ;
case 2 :
mtpmr ( PMRN_PMC2 , val ) ;
break ;
case 3 :
mtpmr ( PMRN_PMC3 , val ) ;
break ;
default :
printk ( KERN_ERR " oops trying to write PMC%d \n " , idx ) ;
}
isync ( ) ;
}
/*
* Write one local control A register
*/
static void write_pmlca ( int idx , unsigned long val )
{
switch ( idx ) {
case 0 :
mtpmr ( PMRN_PMLCA0 , val ) ;
break ;
case 1 :
mtpmr ( PMRN_PMLCA1 , val ) ;
break ;
case 2 :
mtpmr ( PMRN_PMLCA2 , val ) ;
break ;
case 3 :
mtpmr ( PMRN_PMLCA3 , val ) ;
break ;
default :
printk ( KERN_ERR " oops trying to write PMLCA%d \n " , idx ) ;
}
isync ( ) ;
}
/*
* Write one local control B register
*/
static void write_pmlcb ( int idx , unsigned long val )
{
switch ( idx ) {
case 0 :
mtpmr ( PMRN_PMLCB0 , val ) ;
break ;
case 1 :
mtpmr ( PMRN_PMLCB1 , val ) ;
break ;
case 2 :
mtpmr ( PMRN_PMLCB2 , val ) ;
break ;
case 3 :
mtpmr ( PMRN_PMLCB3 , val ) ;
break ;
default :
printk ( KERN_ERR " oops trying to write PMLCB%d \n " , idx ) ;
}
isync ( ) ;
}
static void fsl_emb_pmu_read ( struct perf_event * event )
{
s64 val , delta , prev ;
/*
* Performance monitor interrupts come even when interrupts
* are soft - disabled , as long as interrupts are hard - enabled .
* Therefore we treat them like NMIs .
*/
do {
prev = atomic64_read ( & event - > hw . prev_count ) ;
barrier ( ) ;
val = read_pmc ( event - > hw . idx ) ;
} while ( atomic64_cmpxchg ( & event - > hw . prev_count , prev , val ) ! = prev ) ;
/* The counters are only 32 bits wide */
delta = ( val - prev ) & 0xfffffffful ;
atomic64_add ( delta , & event - > count ) ;
atomic64_sub ( delta , & event - > hw . period_left ) ;
}
/*
* Disable all events to prevent PMU interrupts and to allow
* events to be added or removed .
*/
void hw_perf_disable ( void )
{
struct cpu_hw_events * cpuhw ;
unsigned long flags ;
local_irq_save ( flags ) ;
cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
if ( ! cpuhw - > disabled ) {
cpuhw - > disabled = 1 ;
/*
* Check if we ever enabled the PMU on this cpu .
*/
if ( ! cpuhw - > pmcs_enabled ) {
ppc_enable_pmcs ( ) ;
cpuhw - > pmcs_enabled = 1 ;
}
if ( atomic_read ( & num_events ) ) {
/*
* Set the ' freeze all counters ' bit , and disable
* interrupts . The barrier is to make sure the
* mtpmr has been executed and the PMU has frozen
* the events before we return .
*/
mtpmr ( PMRN_PMGC0 , PMGC0_FAC ) ;
isync ( ) ;
}
}
local_irq_restore ( flags ) ;
}
/*
* Re - enable all events if disable = = 0.
* If we were previously disabled and events were added , then
* put the new config on the PMU .
*/
void hw_perf_enable ( void )
{
struct cpu_hw_events * cpuhw ;
unsigned long flags ;
local_irq_save ( flags ) ;
cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
if ( ! cpuhw - > disabled )
goto out ;
cpuhw - > disabled = 0 ;
ppc_set_pmu_inuse ( cpuhw - > n_events ! = 0 ) ;
if ( cpuhw - > n_events > 0 ) {
mtpmr ( PMRN_PMGC0 , PMGC0_PMIE | PMGC0_FCECE ) ;
isync ( ) ;
}
out :
local_irq_restore ( flags ) ;
}
static int collect_events ( struct perf_event * group , int max_count ,
struct perf_event * ctrs [ ] )
{
int n = 0 ;
struct perf_event * event ;
if ( ! is_software_event ( group ) ) {
if ( n > = max_count )
return - 1 ;
ctrs [ n ] = group ;
n + + ;
}
list_for_each_entry ( event , & group - > sibling_list , group_entry ) {
if ( ! is_software_event ( event ) & &
event - > state ! = PERF_EVENT_STATE_OFF ) {
if ( n > = max_count )
return - 1 ;
ctrs [ n ] = event ;
n + + ;
}
}
return n ;
}
/* perf must be disabled, context locked on entry */
static int fsl_emb_pmu_enable ( struct perf_event * event )
{
struct cpu_hw_events * cpuhw ;
int ret = - EAGAIN ;
int num_counters = ppmu - > n_counter ;
u64 val ;
int i ;
cpuhw = & get_cpu_var ( cpu_hw_events ) ;
if ( event - > hw . config & FSL_EMB_EVENT_RESTRICTED )
num_counters = ppmu - > n_restricted ;
/*
* Allocate counters from top - down , so that restricted - capable
* counters are kept free as long as possible .
*/
for ( i = num_counters - 1 ; i > = 0 ; i - - ) {
if ( cpuhw - > event [ i ] )
continue ;
break ;
}
if ( i < 0 )
goto out ;
event - > hw . idx = i ;
cpuhw - > event [ i ] = event ;
+ + cpuhw - > n_events ;
val = 0 ;
if ( event - > hw . sample_period ) {
s64 left = atomic64_read ( & event - > hw . period_left ) ;
if ( left < 0x80000000L )
val = 0x80000000L - left ;
}
atomic64_set ( & event - > hw . prev_count , val ) ;
write_pmc ( i , val ) ;
perf_event_update_userpage ( event ) ;
write_pmlcb ( i , event - > hw . config > > 32 ) ;
write_pmlca ( i , event - > hw . config_base ) ;
ret = 0 ;
out :
put_cpu_var ( cpu_hw_events ) ;
return ret ;
}
/* perf must be disabled, context locked on entry */
static void fsl_emb_pmu_disable ( struct perf_event * event )
{
struct cpu_hw_events * cpuhw ;
int i = event - > hw . idx ;
if ( i < 0 )
goto out ;
fsl_emb_pmu_read ( event ) ;
cpuhw = & get_cpu_var ( cpu_hw_events ) ;
WARN_ON ( event ! = cpuhw - > event [ event - > hw . idx ] ) ;
write_pmlca ( i , 0 ) ;
write_pmlcb ( i , 0 ) ;
write_pmc ( i , 0 ) ;
cpuhw - > event [ i ] = NULL ;
event - > hw . idx = - 1 ;
/*
* TODO : if at least one restricted event exists , and we
* just freed up a non - restricted - capable counter , and
* there is a restricted - capable counter occupied by
* a non - restricted event , migrate that event to the
* vacated counter .
*/
cpuhw - > n_events - - ;
out :
put_cpu_var ( cpu_hw_events ) ;
}
/*
* Re - enable interrupts on a event after they were throttled
* because they were coming too fast .
*
* Context is locked on entry , but perf is not disabled .
*/
static void fsl_emb_pmu_unthrottle ( struct perf_event * event )
{
s64 val , left ;
unsigned long flags ;
if ( event - > hw . idx < 0 | | ! event - > hw . sample_period )
return ;
local_irq_save ( flags ) ;
perf_disable ( ) ;
fsl_emb_pmu_read ( event ) ;
left = event - > hw . sample_period ;
event - > hw . last_period = left ;
val = 0 ;
if ( left < 0x80000000L )
val = 0x80000000L - left ;
write_pmc ( event - > hw . idx , val ) ;
atomic64_set ( & event - > hw . prev_count , val ) ;
atomic64_set ( & event - > hw . period_left , left ) ;
perf_event_update_userpage ( event ) ;
perf_enable ( ) ;
local_irq_restore ( flags ) ;
}
static struct pmu fsl_emb_pmu = {
. enable = fsl_emb_pmu_enable ,
. disable = fsl_emb_pmu_disable ,
. read = fsl_emb_pmu_read ,
. unthrottle = fsl_emb_pmu_unthrottle ,
} ;
/*
* Release the PMU if this is the last perf_event .
*/
static void hw_perf_event_destroy ( struct perf_event * event )
{
if ( ! atomic_add_unless ( & num_events , - 1 , 1 ) ) {
mutex_lock ( & pmc_reserve_mutex ) ;
if ( atomic_dec_return ( & num_events ) = = 0 )
release_pmc_hardware ( ) ;
mutex_unlock ( & pmc_reserve_mutex ) ;
}
}
/*
* Translate a generic cache event_id config to a raw event_id code .
*/
static int hw_perf_cache_event ( u64 config , u64 * eventp )
{
unsigned long type , op , result ;
int ev ;
if ( ! ppmu - > cache_events )
return - EINVAL ;
/* unpack config */
type = config & 0xff ;
op = ( config > > 8 ) & 0xff ;
result = ( config > > 16 ) & 0xff ;
if ( type > = PERF_COUNT_HW_CACHE_MAX | |
op > = PERF_COUNT_HW_CACHE_OP_MAX | |
result > = PERF_COUNT_HW_CACHE_RESULT_MAX )
return - EINVAL ;
ev = ( * ppmu - > cache_events ) [ type ] [ op ] [ result ] ;
if ( ev = = 0 )
return - EOPNOTSUPP ;
if ( ev = = - 1 )
return - EINVAL ;
* eventp = ev ;
return 0 ;
}
const struct pmu * hw_perf_event_init ( struct perf_event * event )
{
u64 ev ;
struct perf_event * events [ MAX_HWEVENTS ] ;
int n ;
int err ;
int num_restricted ;
int i ;
switch ( event - > attr . type ) {
case PERF_TYPE_HARDWARE :
ev = event - > attr . config ;
if ( ev > = ppmu - > n_generic | | ppmu - > generic_events [ ev ] = = 0 )
return ERR_PTR ( - EOPNOTSUPP ) ;
ev = ppmu - > generic_events [ ev ] ;
break ;
case PERF_TYPE_HW_CACHE :
err = hw_perf_cache_event ( event - > attr . config , & ev ) ;
if ( err )
return ERR_PTR ( err ) ;
break ;
case PERF_TYPE_RAW :
ev = event - > attr . config ;
break ;
default :
return ERR_PTR ( - EINVAL ) ;
}
event - > hw . config = ppmu - > xlate_event ( ev ) ;
if ( ! ( event - > hw . config & FSL_EMB_EVENT_VALID ) )
return ERR_PTR ( - EINVAL ) ;
/*
* If this is in a group , check if it can go on with all the
* other hardware events in the group . We assume the event
* hasn ' t been linked into its leader ' s sibling list at this point .
*/
n = 0 ;
if ( event - > group_leader ! = event ) {
n = collect_events ( event - > group_leader ,
ppmu - > n_counter - 1 , events ) ;
if ( n < 0 )
return ERR_PTR ( - EINVAL ) ;
}
if ( event - > hw . config & FSL_EMB_EVENT_RESTRICTED ) {
num_restricted = 0 ;
for ( i = 0 ; i < n ; i + + ) {
if ( events [ i ] - > hw . config & FSL_EMB_EVENT_RESTRICTED )
num_restricted + + ;
}
if ( num_restricted > = ppmu - > n_restricted )
return ERR_PTR ( - EINVAL ) ;
}
event - > hw . idx = - 1 ;
event - > hw . config_base = PMLCA_CE | PMLCA_FCM1 |
( u32 ) ( ( ev < < 16 ) & PMLCA_EVENT_MASK ) ;
if ( event - > attr . exclude_user )
event - > hw . config_base | = PMLCA_FCU ;
if ( event - > attr . exclude_kernel )
event - > hw . config_base | = PMLCA_FCS ;
if ( event - > attr . exclude_idle )
return ERR_PTR ( - ENOTSUPP ) ;
event - > hw . last_period = event - > hw . sample_period ;
atomic64_set ( & event - > hw . period_left , event - > hw . last_period ) ;
/*
* See if we need to reserve the PMU .
* If no events are currently in use , then we have to take a
* mutex to ensure that we don ' t race with another task doing
* reserve_pmc_hardware or release_pmc_hardware .
*/
err = 0 ;
if ( ! atomic_inc_not_zero ( & num_events ) ) {
mutex_lock ( & pmc_reserve_mutex ) ;
if ( atomic_read ( & num_events ) = = 0 & &
reserve_pmc_hardware ( perf_event_interrupt ) )
err = - EBUSY ;
else
atomic_inc ( & num_events ) ;
mutex_unlock ( & pmc_reserve_mutex ) ;
mtpmr ( PMRN_PMGC0 , PMGC0_FAC ) ;
isync ( ) ;
}
event - > destroy = hw_perf_event_destroy ;
if ( err )
return ERR_PTR ( err ) ;
return & fsl_emb_pmu ;
}
/*
* A counter has overflowed ; update its count and record
* things if requested . Note that interrupts are hard - disabled
* here so there is no possibility of being interrupted .
*/
static void record_and_restart ( struct perf_event * event , unsigned long val ,
struct pt_regs * regs , int nmi )
{
u64 period = event - > hw . sample_period ;
s64 prev , delta , left ;
int record = 0 ;
/* we don't have to worry about interrupts here */
prev = atomic64_read ( & event - > hw . prev_count ) ;
delta = ( val - prev ) & 0xfffffffful ;
atomic64_add ( delta , & event - > count ) ;
/*
* See if the total period for this event has expired ,
* and update for the next period .
*/
val = 0 ;
left = atomic64_read ( & event - > hw . period_left ) - delta ;
if ( period ) {
if ( left < = 0 ) {
left + = period ;
if ( left < = 0 )
left = period ;
record = 1 ;
}
if ( left < 0x80000000LL )
val = 0x80000000LL - left ;
}
/*
* Finally record data if requested .
*/
if ( record ) {
2010-07-09 10:21:21 +02:00
struct perf_sample_data data ;
perf_sample_data_init ( & data , 0 ) ;
2010-02-25 18:09:45 -06:00
if ( perf_event_overflow ( event , nmi , & data , regs ) ) {
/*
* Interrupts are coming too fast - throttle them
* by setting the event to 0 , so it will be
* at least 2 ^ 30 cycles until the next interrupt
* ( assuming each event counts at most 2 counts
* per cycle ) .
*/
val = 0 ;
left = ~ 0ULL > > 1 ;
}
}
write_pmc ( event - > hw . idx , val ) ;
atomic64_set ( & event - > hw . prev_count , val ) ;
atomic64_set ( & event - > hw . period_left , left ) ;
perf_event_update_userpage ( event ) ;
}
static void perf_event_interrupt ( struct pt_regs * regs )
{
int i ;
struct cpu_hw_events * cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
struct perf_event * event ;
unsigned long val ;
int found = 0 ;
int nmi ;
nmi = perf_intr_is_nmi ( regs ) ;
if ( nmi )
nmi_enter ( ) ;
else
irq_enter ( ) ;
for ( i = 0 ; i < ppmu - > n_counter ; + + i ) {
event = cpuhw - > event [ i ] ;
val = read_pmc ( i ) ;
if ( ( int ) val < 0 ) {
if ( event ) {
/* event has overflowed */
found = 1 ;
record_and_restart ( event , val , regs , nmi ) ;
} else {
/*
* Disabled counter is negative ,
* reset it just in case .
*/
write_pmc ( i , 0 ) ;
}
}
}
/* PMM will keep counters frozen until we return from the interrupt. */
mtmsr ( mfmsr ( ) | MSR_PMM ) ;
mtpmr ( PMRN_PMGC0 , PMGC0_PMIE | PMGC0_FCECE ) ;
isync ( ) ;
if ( nmi )
nmi_exit ( ) ;
else
irq_exit ( ) ;
}
void hw_perf_event_setup ( int cpu )
{
struct cpu_hw_events * cpuhw = & per_cpu ( cpu_hw_events , cpu ) ;
memset ( cpuhw , 0 , sizeof ( * cpuhw ) ) ;
}
int register_fsl_emb_pmu ( struct fsl_emb_pmu * pmu )
{
if ( ppmu )
return - EBUSY ; /* something's already registered */
ppmu = pmu ;
pr_info ( " %s performance monitor hardware support registered \n " ,
pmu - > name ) ;
return 0 ;
}