2012-03-23 11:13:06 +01:00
/*
* Performance event support for s390x
*
2013-12-12 17:54:57 +01:00
* Copyright IBM Corp . 2012 , 2013
2012-03-23 11:13:06 +01:00
* Author ( s ) : Hendrik Brueckner < brueckner @ linux . vnet . ibm . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License ( version 2 only )
* as published by the Free Software Foundation .
*/
# define KMSG_COMPONENT "perf"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
# include <linux/kernel.h>
# include <linux/perf_event.h>
2013-06-12 13:54:56 +02:00
# include <linux/kvm_host.h>
2012-03-23 11:13:06 +01:00
# include <linux/percpu.h>
# include <linux/export.h>
2013-12-13 12:38:39 +01:00
# include <linux/seq_file.h>
2013-12-12 16:32:47 +01:00
# include <linux/spinlock.h>
2013-12-11 12:44:40 +01:00
# include <linux/sysfs.h>
2012-03-23 11:13:06 +01:00
# include <asm/irq.h>
# include <asm/cpu_mf.h>
# include <asm/lowcore.h>
# include <asm/processor.h>
2013-12-13 12:38:39 +01:00
# include <asm/sysinfo.h>
2012-03-23 11:13:06 +01:00
const char * perf_pmu_name ( void )
{
if ( cpum_cf_avail ( ) | | cpum_sf_avail ( ) )
2013-12-13 12:38:39 +01:00
return " CPU-Measurement Facilities (CPU-MF) " ;
2012-03-23 11:13:06 +01:00
return " pmu " ;
}
EXPORT_SYMBOL ( perf_pmu_name ) ;
int perf_num_counters ( void )
{
int num = 0 ;
if ( cpum_cf_avail ( ) )
num + = PERF_CPUM_CF_MAX_CTR ;
2013-12-12 16:32:47 +01:00
if ( cpum_sf_avail ( ) )
num + = PERF_CPUM_SF_MAX_CTR ;
2012-03-23 11:13:06 +01:00
return num ;
}
EXPORT_SYMBOL ( perf_num_counters ) ;
2013-06-12 13:54:56 +02:00
static struct kvm_s390_sie_block * sie_block ( struct pt_regs * regs )
{
struct stack_frame * stack = ( struct stack_frame * ) regs - > gprs [ 15 ] ;
if ( ! stack )
return NULL ;
return ( struct kvm_s390_sie_block * ) stack - > empty1 [ 0 ] ;
}
static bool is_in_guest ( struct pt_regs * regs )
{
if ( user_mode ( regs ) )
return false ;
2013-11-01 10:08:20 +01:00
# if IS_ENABLED(CONFIG_KVM)
2013-07-25 11:16:48 +02:00
return instruction_pointer ( regs ) = = ( unsigned long ) & sie_exit ;
# else
return false ;
# endif
2013-06-12 13:54:56 +02:00
}
static unsigned long guest_is_user_mode ( struct pt_regs * regs )
{
return sie_block ( regs ) - > gpsw . mask & PSW_MASK_PSTATE ;
}
static unsigned long instruction_pointer_guest ( struct pt_regs * regs )
{
return sie_block ( regs ) - > gpsw . addr & PSW_ADDR_INSN ;
}
unsigned long perf_instruction_pointer ( struct pt_regs * regs )
{
return is_in_guest ( regs ) ? instruction_pointer_guest ( regs )
: instruction_pointer ( regs ) ;
}
static unsigned long perf_misc_guest_flags ( struct pt_regs * regs )
{
return guest_is_user_mode ( regs ) ? PERF_RECORD_MISC_GUEST_USER
: PERF_RECORD_MISC_GUEST_KERNEL ;
}
2013-12-12 17:54:57 +01:00
static unsigned long perf_misc_flags_sf ( struct pt_regs * regs )
{
struct perf_sf_sde_regs * sde_regs ;
unsigned long flags ;
sde_regs = ( struct perf_sf_sde_regs * ) & regs - > int_parm_long ;
if ( sde_regs - > in_guest )
flags = user_mode ( regs ) ? PERF_RECORD_MISC_GUEST_USER
: PERF_RECORD_MISC_GUEST_KERNEL ;
else
flags = user_mode ( regs ) ? PERF_RECORD_MISC_USER
: PERF_RECORD_MISC_KERNEL ;
return flags ;
}
2013-06-12 13:54:56 +02:00
unsigned long perf_misc_flags ( struct pt_regs * regs )
{
2013-12-12 17:54:57 +01:00
/* Check if the cpum_sf PMU has created the pt_regs structure.
* In this case , perf misc flags can be easily extracted . Otherwise ,
* do regular checks on the pt_regs content .
*/
if ( regs - > int_code = = 0x1407 & & regs - > int_parm = = CPU_MF_INT_SF_PRA )
if ( ! regs - > gprs [ 15 ] )
return perf_misc_flags_sf ( regs ) ;
2013-06-12 13:54:56 +02:00
if ( is_in_guest ( regs ) )
return perf_misc_guest_flags ( regs ) ;
return user_mode ( regs ) ? PERF_RECORD_MISC_USER
: PERF_RECORD_MISC_KERNEL ;
}
2014-03-11 10:49:20 +01:00
static void print_debug_cf ( void )
2012-03-23 11:13:06 +01:00
{
struct cpumf_ctr_info cf_info ;
2013-12-12 16:32:47 +01:00
int cpu = smp_processor_id ( ) ;
2012-03-23 11:13:06 +01:00
memset ( & cf_info , 0 , sizeof ( cf_info ) ) ;
2013-09-02 09:48:48 +02:00
if ( ! qctri ( & cf_info ) )
2012-03-23 11:13:06 +01:00
pr_info ( " CPU[%i] CPUM_CF: ver=%u.%u A=%04x E=%04x C=%04x \n " ,
cpu , cf_info . cfvn , cf_info . csvn ,
cf_info . auth_ctl , cf_info . enable_ctl , cf_info . act_ctl ) ;
2013-12-12 16:32:47 +01:00
}
2012-03-23 11:13:06 +01:00
2013-12-12 16:32:47 +01:00
static void print_debug_sf ( void )
{
struct hws_qsi_info_block si ;
int cpu = smp_processor_id ( ) ;
memset ( & si , 0 , sizeof ( si ) ) ;
2013-12-13 11:42:44 +01:00
if ( qsi ( & si ) )
2013-12-12 16:32:47 +01:00
return ;
2014-01-08 16:45:39 +01:00
pr_info ( " CPU[%i] CPUM_SF: basic=%i diag=%i min=%lu max=%lu cpu_speed=%u \n " ,
2013-12-13 11:42:44 +01:00
cpu , si . as , si . ad , si . min_sampl_rate , si . max_sampl_rate ,
si . cpu_speed ) ;
if ( si . as )
pr_info ( " CPU[%i] CPUM_SF: Basic-sampling: a=%i e=%i c=%i "
2014-01-08 16:45:39 +01:00
" bsdes=%i tear=%016lx dear=%016lx \n " , cpu ,
2013-12-13 11:42:44 +01:00
si . as , si . es , si . cs , si . bsdes , si . tear , si . dear ) ;
if ( si . ad )
pr_info ( " CPU[%i] CPUM_SF: Diagnostic-sampling: a=%i e=%i c=%i "
2014-01-08 16:45:39 +01:00
" dsdes=%i tear=%016lx dear=%016lx \n " , cpu ,
2013-12-13 11:42:44 +01:00
si . ad , si . ed , si . cd , si . dsdes , si . tear , si . dear ) ;
2013-12-12 16:32:47 +01:00
}
void perf_event_print_debug ( void )
{
unsigned long flags ;
local_irq_save ( flags ) ;
if ( cpum_cf_avail ( ) )
print_debug_cf ( ) ;
if ( cpum_sf_avail ( ) )
print_debug_sf ( ) ;
2012-03-23 11:13:06 +01:00
local_irq_restore ( flags ) ;
}
2013-12-13 12:38:39 +01:00
/* Service level infrastructure */
static void sl_print_counter ( struct seq_file * m )
{
struct cpumf_ctr_info ci ;
memset ( & ci , 0 , sizeof ( ci ) ) ;
if ( qctri ( & ci ) )
return ;
seq_printf ( m , " CPU-MF: Counter facility: version=%u.%u "
" authorization=%04x \n " , ci . cfvn , ci . csvn , ci . auth_ctl ) ;
}
static void sl_print_sampling ( struct seq_file * m )
{
struct hws_qsi_info_block si ;
memset ( & si , 0 , sizeof ( si ) ) ;
if ( qsi ( & si ) )
return ;
if ( ! si . as & & ! si . ad )
return ;
seq_printf ( m , " CPU-MF: Sampling facility: min_rate=%lu max_rate=%lu "
" cpu_speed=%u \n " , si . min_sampl_rate , si . max_sampl_rate ,
si . cpu_speed ) ;
if ( si . as )
seq_printf ( m , " CPU-MF: Sampling facility: mode=basic "
" sample_size=%u \n " , si . bsdes ) ;
if ( si . ad )
seq_printf ( m , " CPU-MF: Sampling facility: mode=diagnostic "
" sample_size=%u \n " , si . dsdes ) ;
}
static void service_level_perf_print ( struct seq_file * m ,
struct service_level * sl )
{
if ( cpum_cf_avail ( ) )
sl_print_counter ( m ) ;
if ( cpum_sf_avail ( ) )
sl_print_sampling ( m ) ;
}
static struct service_level service_level_perf = {
. seq_print = service_level_perf_print ,
} ;
static int __init service_level_perf_register ( void )
{
return register_service_level ( & service_level_perf ) ;
}
arch_initcall ( service_level_perf_register ) ;
2012-03-23 11:13:06 +01:00
/* See also arch/s390/kernel/traps.c */
static unsigned long __store_trace ( struct perf_callchain_entry * entry ,
unsigned long sp ,
unsigned long low , unsigned long high )
{
struct stack_frame * sf ;
struct pt_regs * regs ;
while ( 1 ) {
sp = sp & PSW_ADDR_INSN ;
if ( sp < low | | sp > high - sizeof ( * sf ) )
return sp ;
sf = ( struct stack_frame * ) sp ;
perf_callchain_store ( entry , sf - > gprs [ 8 ] & PSW_ADDR_INSN ) ;
/* Follow the backchain. */
while ( 1 ) {
low = sp ;
sp = sf - > back_chain & PSW_ADDR_INSN ;
if ( ! sp )
break ;
if ( sp < = low | | sp > high - sizeof ( * sf ) )
return sp ;
sf = ( struct stack_frame * ) sp ;
perf_callchain_store ( entry ,
sf - > gprs [ 8 ] & PSW_ADDR_INSN ) ;
}
/* Zero backchain detected, check for interrupt frame. */
sp = ( unsigned long ) ( sf + 1 ) ;
if ( sp < = low | | sp > high - sizeof ( * regs ) )
return sp ;
regs = ( struct pt_regs * ) sp ;
perf_callchain_store ( entry , sf - > gprs [ 8 ] & PSW_ADDR_INSN ) ;
low = sp ;
sp = regs - > gprs [ 15 ] ;
}
}
void perf_callchain_kernel ( struct perf_callchain_entry * entry ,
struct pt_regs * regs )
{
unsigned long head ;
struct stack_frame * head_sf ;
if ( user_mode ( regs ) )
return ;
head = regs - > gprs [ 15 ] ;
head_sf = ( struct stack_frame * ) head ;
if ( ! head_sf | | ! head_sf - > back_chain )
return ;
head = head_sf - > back_chain ;
head = __store_trace ( entry , head , S390_lowcore . async_stack - ASYNC_SIZE ,
S390_lowcore . async_stack ) ;
__store_trace ( entry , head , S390_lowcore . thread_info ,
S390_lowcore . thread_info + THREAD_SIZE ) ;
}
2013-12-11 12:44:40 +01:00
/* Perf defintions for PMU event attributes in sysfs */
ssize_t cpumf_events_sysfs_show ( struct device * dev ,
struct device_attribute * attr , char * page )
{
struct perf_pmu_events_attr * pmu_attr ;
pmu_attr = container_of ( attr , struct perf_pmu_events_attr , attr ) ;
return sprintf ( page , " event=0x%04llx,name=%s \n " ,
pmu_attr - > id , attr - > attr . name ) ;
}
2013-12-12 16:52:48 +01:00
/* Reserve/release functions for sharing perf hardware */
static DEFINE_SPINLOCK ( perf_hw_owner_lock ) ;
static void * perf_sampling_owner ;
int perf_reserve_sampling ( void )
{
int err ;
err = 0 ;
spin_lock ( & perf_hw_owner_lock ) ;
if ( perf_sampling_owner ) {
pr_warn ( " The sampling facility is already reserved by %p \n " ,
perf_sampling_owner ) ;
err = - EBUSY ;
} else
perf_sampling_owner = __builtin_return_address ( 0 ) ;
spin_unlock ( & perf_hw_owner_lock ) ;
return err ;
}
EXPORT_SYMBOL ( perf_reserve_sampling ) ;
void perf_release_sampling ( void )
{
spin_lock ( & perf_hw_owner_lock ) ;
WARN_ON ( ! perf_sampling_owner ) ;
perf_sampling_owner = NULL ;
spin_unlock ( & perf_hw_owner_lock ) ;
}
EXPORT_SYMBOL ( perf_release_sampling ) ;