@ -19,6 +19,11 @@
# include <asm/firmware.h>
# include <asm/ptrace.h>
# define BHRB_MAX_ENTRIES 32
# define BHRB_TARGET 0x0000000000000002
# define BHRB_PREDICTION 0x0000000000000001
# define BHRB_EA 0xFFFFFFFFFFFFFFFC
struct cpu_hw_events {
int n_events ;
int n_percpu ;
@ -38,7 +43,15 @@ struct cpu_hw_events {
unsigned int group_flag ;
int n_txn_start ;
/* BHRB bits */
u64 bhrb_filter ; /* BHRB HW branch filter */
int bhrb_users ;
void * bhrb_context ;
struct perf_branch_stack bhrb_stack ;
struct perf_branch_entry bhrb_entries [ BHRB_MAX_ENTRIES ] ;
} ;
DEFINE_PER_CPU ( struct cpu_hw_events , cpu_hw_events ) ;
struct power_pmu * ppmu ;
@ -858,6 +871,9 @@ static void power_pmu_enable(struct pmu *pmu)
}
out :
if ( cpuhw - > bhrb_users )
ppmu - > config_bhrb ( cpuhw - > bhrb_filter ) ;
local_irq_restore ( flags ) ;
}
@ -888,6 +904,47 @@ static int collect_events(struct perf_event *group, int max_count,
return n ;
}
/* Reset all possible BHRB entries */
static void power_pmu_bhrb_reset ( void )
{
asm volatile ( PPC_CLRBHRB ) ;
}
void power_pmu_bhrb_enable ( struct perf_event * event )
{
struct cpu_hw_events * cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
if ( ! ppmu - > bhrb_nr )
return ;
/* Clear BHRB if we changed task context to avoid data leaks */
if ( event - > ctx - > task & & cpuhw - > bhrb_context ! = event - > ctx ) {
power_pmu_bhrb_reset ( ) ;
cpuhw - > bhrb_context = event - > ctx ;
}
cpuhw - > bhrb_users + + ;
}
void power_pmu_bhrb_disable ( struct perf_event * event )
{
struct cpu_hw_events * cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
if ( ! ppmu - > bhrb_nr )
return ;
cpuhw - > bhrb_users - - ;
WARN_ON_ONCE ( cpuhw - > bhrb_users < 0 ) ;
if ( ! cpuhw - > disabled & & ! cpuhw - > bhrb_users ) {
/* BHRB cannot be turned off when other
* events are active on the PMU .
*/
/* avoid stale pointer */
cpuhw - > bhrb_context = NULL ;
}
}
/*
* Add a event to the PMU .
* If all events are not already frozen , then we disable and
@ -947,6 +1004,9 @@ nocheck:
ret = 0 ;
out :
if ( has_branch_stack ( event ) )
power_pmu_bhrb_enable ( event ) ;
perf_pmu_enable ( event - > pmu ) ;
local_irq_restore ( flags ) ;
return ret ;
@ -999,6 +1059,9 @@ static void power_pmu_del(struct perf_event *event, int ef_flags)
cpuhw - > mmcr [ 0 ] & = ~ ( MMCR0_PMXE | MMCR0_FCECE ) ;
}
if ( has_branch_stack ( event ) )
power_pmu_bhrb_disable ( event ) ;
perf_pmu_enable ( event - > pmu ) ;
local_irq_restore ( flags ) ;
}
@ -1117,6 +1180,15 @@ int power_pmu_commit_txn(struct pmu *pmu)
return 0 ;
}
/* Called from ctxsw to prevent one process's branch entries to
* mingle with the other process ' s entries during context switch .
*/
void power_pmu_flush_branch_stack ( void )
{
if ( ppmu - > bhrb_nr )
power_pmu_bhrb_reset ( ) ;
}
/*
* Return 1 if we might be able to put event on a limited PMC ,
* or 0 if not .
@ -1231,9 +1303,11 @@ static int power_pmu_event_init(struct perf_event *event)
if ( ! ppmu )
return - ENOENT ;
/* does not support taken branch sampling */
if ( has_branch_stack ( event ) )
return - EOPNOTSUPP ;
if ( has_branch_stack ( event ) ) {
/* PMU has BHRB enabled */
if ( ! ( ppmu - > flags & PPMU_BHRB ) )
return - EOPNOTSUPP ;
}
switch ( event - > attr . type ) {
case PERF_TYPE_HARDWARE :
@ -1314,6 +1388,15 @@ static int power_pmu_event_init(struct perf_event *event)
cpuhw = & get_cpu_var ( cpu_hw_events ) ;
err = power_check_constraints ( cpuhw , events , cflags , n + 1 ) ;
if ( has_branch_stack ( event ) ) {
cpuhw - > bhrb_filter = ppmu - > bhrb_filter_map (
event - > attr . branch_sample_type ) ;
if ( cpuhw - > bhrb_filter = = - 1 )
return - EOPNOTSUPP ;
}
put_cpu_var ( cpu_hw_events ) ;
if ( err )
return - EINVAL ;
@ -1372,8 +1455,79 @@ struct pmu power_pmu = {
. cancel_txn = power_pmu_cancel_txn ,
. commit_txn = power_pmu_commit_txn ,
. event_idx = power_pmu_event_idx ,
. flush_branch_stack = power_pmu_flush_branch_stack ,
} ;
/* Processing BHRB entries */
void power_pmu_bhrb_read ( struct cpu_hw_events * cpuhw )
{
u64 val ;
u64 addr ;
int r_index , u_index , target , pred ;
r_index = 0 ;
u_index = 0 ;
while ( r_index < ppmu - > bhrb_nr ) {
/* Assembly read function */
val = read_bhrb ( r_index ) ;
/* Terminal marker: End of valid BHRB entries */
if ( val = = 0 ) {
break ;
} else {
/* BHRB field break up */
addr = val & BHRB_EA ;
pred = val & BHRB_PREDICTION ;
target = val & BHRB_TARGET ;
/* Probable Missed entry: Not applicable for POWER8 */
if ( ( addr = = 0 ) & & ( target = = 0 ) & & ( pred = = 1 ) ) {
r_index + + ;
continue ;
}
/* Real Missed entry: Power8 based missed entry */
if ( ( addr = = 0 ) & & ( target = = 1 ) & & ( pred = = 1 ) ) {
r_index + + ;
continue ;
}
/* Reserved condition: Not a valid entry */
if ( ( addr = = 0 ) & & ( target = = 1 ) & & ( pred = = 0 ) ) {
r_index + + ;
continue ;
}
/* Is a target address */
if ( val & BHRB_TARGET ) {
/* First address cannot be a target address */
if ( r_index = = 0 ) {
r_index + + ;
continue ;
}
/* Update target address for the previous entry */
cpuhw - > bhrb_entries [ u_index - 1 ] . to = addr ;
cpuhw - > bhrb_entries [ u_index - 1 ] . mispred = pred ;
cpuhw - > bhrb_entries [ u_index - 1 ] . predicted = ~ pred ;
/* Dont increment u_index */
r_index + + ;
} else {
/* Update address, flags for current entry */
cpuhw - > bhrb_entries [ u_index ] . from = addr ;
cpuhw - > bhrb_entries [ u_index ] . mispred = pred ;
cpuhw - > bhrb_entries [ u_index ] . predicted = ~ pred ;
/* Successfully popullated one entry */
u_index + + ;
r_index + + ;
}
}
}
cpuhw - > bhrb_stack . nr = u_index ;
return ;
}
/*
* A counter has overflowed ; update its count and record
@ -1433,6 +1587,13 @@ static void record_and_restart(struct perf_event *event, unsigned long val,
if ( event - > attr . sample_type & PERF_SAMPLE_ADDR )
perf_get_data_addr ( regs , & data . addr ) ;
if ( event - > attr . sample_type & PERF_SAMPLE_BRANCH_STACK ) {
struct cpu_hw_events * cpuhw ;
cpuhw = & __get_cpu_var ( cpu_hw_events ) ;
power_pmu_bhrb_read ( cpuhw ) ;
data . br_stack = & cpuhw - > bhrb_stack ;
}
if ( perf_event_overflow ( event , & data , regs ) )
power_pmu_stop ( event , 0 ) ;
}