2018-10-29 13:16:38 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* Performance event support for s390x - CPU - measurement Counter Sets
*
* Copyright IBM Corp . 2019
* Author ( s ) : Hendrik Brueckner < brueckner @ linux . ibm . com >
* Thomas Richer < tmricht @ linux . ibm . com >
*/
# define KMSG_COMPONENT "cpum_cf_diag"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
# include <linux/kernel.h>
# include <linux/kernel_stat.h>
# include <linux/percpu.h>
# include <linux/notifier.h>
# include <linux/init.h>
# include <linux/export.h>
# include <linux/slab.h>
# include <linux/processor.h>
# include <asm/ctl_reg.h>
# include <asm/irq.h>
# include <asm/cpu_mcf.h>
# include <asm/timex.h>
# include <asm/debug.h>
# define CF_DIAG_CTRSET_DEF 0xfeef /* Counter set header mark */
static unsigned int cf_diag_cpu_speed ;
static debug_info_t * cf_diag_dbg ;
struct cf_diag_csd { /* Counter set data per CPU */
size_t used ; /* Bytes used in data/start */
unsigned char start [ PAGE_SIZE ] ; /* Counter set at event start */
unsigned char data [ PAGE_SIZE ] ; /* Counter set at event delete */
} ;
2019-07-17 20:05:11 +02:00
static DEFINE_PER_CPU ( struct cf_diag_csd , cf_diag_csd ) ;
2018-10-29 13:16:38 +00:00
/* Counter sets are stored as data stream in a page sized memory buffer and
* exported to user space via raw data attached to the event sample data .
* Each counter set starts with an eight byte header consisting of :
* - a two byte eye catcher ( 0xfeef )
* - a one byte counter set number
* - a two byte counter set size ( indicates the number of counters in this set )
* - a three byte reserved value ( must be zero ) to make the header the same
* size as a counter value .
* All counter values are eight byte in size .
*
* All counter sets are followed by a 64 byte trailer .
* The trailer consists of a :
* - flag field indicating valid fields when corresponding bit set
* - the counter facility first and second version number
* - the CPU speed if nonzero
* - the time stamp the counter sets have been collected
* - the time of day ( TOD ) base value
* - the machine type .
*
* The counter sets are saved when the process is prepared to be executed on a
* CPU and saved again when the process is going to be removed from a CPU .
* The difference of both counter sets are calculated and stored in the event
* sample data area .
*/
struct cf_ctrset_entry { /* CPU-M CF counter set entry (8 byte) */
unsigned int def : 16 ; /* 0-15 Data Entry Format */
unsigned int set : 16 ; /* 16-31 Counter set identifier */
unsigned int ctr : 16 ; /* 32-47 Number of stored counters */
unsigned int res1 : 16 ; /* 48-63 Reserved */
} ;
struct cf_trailer_entry { /* CPU-M CF_DIAG trailer (64 byte) */
/* 0 - 7 */
union {
struct {
unsigned int clock_base : 1 ; /* TOD clock base set */
unsigned int speed : 1 ; /* CPU speed set */
/* Measurement alerts */
unsigned int mtda : 1 ; /* Loss of MT ctr. data alert */
unsigned int caca : 1 ; /* Counter auth. change alert */
unsigned int lcda : 1 ; /* Loss of counter data alert */
} ;
unsigned long flags ; /* 0-63 All indicators */
} ;
/* 8 - 15 */
unsigned int cfvn : 16 ; /* 64-79 Ctr First Version */
unsigned int csvn : 16 ; /* 80-95 Ctr Second Version */
unsigned int cpu_speed : 32 ; /* 96-127 CPU speed */
/* 16 - 23 */
unsigned long timestamp ; /* 128-191 Timestamp (TOD) */
/* 24 - 55 */
union {
struct {
unsigned long progusage1 ;
unsigned long progusage2 ;
unsigned long progusage3 ;
unsigned long tod_base ;
} ;
unsigned long progusage [ 4 ] ;
} ;
/* 56 - 63 */
unsigned int mach_type : 16 ; /* Machine type */
unsigned int res1 : 16 ; /* Reserved */
unsigned int res2 : 32 ; /* Reserved */
} ;
/* Create the trailer data at the end of a page. */
static void cf_diag_trailer ( struct cf_trailer_entry * te )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
struct cpuid cpuid ;
te - > cfvn = cpuhw - > info . cfvn ; /* Counter version numbers */
te - > csvn = cpuhw - > info . csvn ;
get_cpu_id ( & cpuid ) ; /* Machine type */
te - > mach_type = cpuid . machine ;
te - > cpu_speed = cf_diag_cpu_speed ;
if ( te - > cpu_speed )
te - > speed = 1 ;
te - > clock_base = 1 ; /* Save clock base */
memcpy ( & te - > tod_base , & tod_clock_base [ 1 ] , 8 ) ;
store_tod_clock ( ( __u64 * ) & te - > timestamp ) ;
}
/*
* Change the CPUMF state to active .
* Enable and activate the CPU - counter sets according
* to the per - cpu control state .
*/
static void cf_diag_enable ( struct pmu * pmu )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
int err ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s pmu %p cpu %d flags %#x state %#llx \n " ,
__func__ , pmu , smp_processor_id ( ) , cpuhw - > flags ,
cpuhw - > state ) ;
if ( cpuhw - > flags & PMU_F_ENABLED )
return ;
err = lcctl ( cpuhw - > state ) ;
if ( err ) {
pr_err ( " Enabling the performance measuring unit "
" failed with rc=%x \n " , err ) ;
return ;
}
cpuhw - > flags | = PMU_F_ENABLED ;
}
/*
* Change the CPUMF state to inactive .
* Disable and enable ( inactive ) the CPU - counter sets according
* to the per - cpu control state .
*/
static void cf_diag_disable ( struct pmu * pmu )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
u64 inactive ;
int err ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s pmu %p cpu %d flags %#x state %#llx \n " ,
__func__ , pmu , smp_processor_id ( ) , cpuhw - > flags ,
cpuhw - > state ) ;
if ( ! ( cpuhw - > flags & PMU_F_ENABLED ) )
return ;
inactive = cpuhw - > state & ~ ( ( 1 < < CPUMF_LCCTL_ENABLE_SHIFT ) - 1 ) ;
err = lcctl ( inactive ) ;
if ( err ) {
pr_err ( " Disabling the performance measuring unit "
" failed with rc=%x \n " , err ) ;
return ;
}
cpuhw - > flags & = ~ PMU_F_ENABLED ;
}
/* Number of perf events counting hardware events */
static atomic_t cf_diag_events = ATOMIC_INIT ( 0 ) ;
/* Release the PMU if event is the last perf event */
static void cf_diag_perf_event_destroy ( struct perf_event * event )
{
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s event %p cpu %d cf_diag_events %d \n " ,
__func__ , event , event - > cpu ,
atomic_read ( & cf_diag_events ) ) ;
if ( atomic_dec_return ( & cf_diag_events ) = = 0 )
__kernel_cpumcf_end ( ) ;
}
/* Setup the event. Test for authorized counter sets and only include counter
* sets which are authorized at the time of the setup . Including unauthorized
* counter sets result in specification exception ( and panic ) .
*/
static int __hw_perf_event_init ( struct perf_event * event )
{
struct perf_event_attr * attr = & event - > attr ;
2019-03-18 15:50:27 +01:00
struct cpu_cf_events * cpuhw ;
2018-10-29 13:16:38 +00:00
enum cpumf_ctr_set i ;
int err = 0 ;
2019-03-18 15:50:27 +01:00
debug_sprintf_event ( cf_diag_dbg , 5 , " %s event %p cpu %d \n " , __func__ ,
event , event - > cpu ) ;
2018-10-29 13:16:38 +00:00
event - > hw . config = attr - > config ;
event - > hw . config_base = 0 ;
2019-03-18 15:50:27 +01:00
/* Add all authorized counter sets to config_base. The
* the hardware init function is either called per - cpu or just once
* for all CPUS ( event - > cpu = = - 1 ) . This depends on the whether
* counting is started for all CPUs or on a per workload base where
* the perf event moves from one CPU to another CPU .
* Checking the authorization on any CPU is fine as the hardware
* applies the same authorization settings to all CPUs .
*/
cpuhw = & get_cpu_var ( cpu_cf_events ) ;
2018-10-29 13:16:38 +00:00
for ( i = CPUMF_CTR_SET_BASIC ; i < CPUMF_CTR_SET_MAX ; + + i )
if ( cpuhw - > info . auth_ctl & cpumf_ctr_ctl [ i ] )
event - > hw . config_base | = cpumf_ctr_ctl [ i ] ;
2019-03-18 15:50:27 +01:00
put_cpu_var ( cpu_cf_events ) ;
2018-10-29 13:16:38 +00:00
/* No authorized counter sets, nothing to count/sample */
if ( ! event - > hw . config_base ) {
err = - EINVAL ;
goto out ;
}
/* Set sample_period to indicate sampling */
event - > hw . sample_period = attr - > sample_period ;
local64_set ( & event - > hw . period_left , event - > hw . sample_period ) ;
event - > hw . last_period = event - > hw . sample_period ;
out :
debug_sprintf_event ( cf_diag_dbg , 5 , " %s err %d config_base %#lx \n " ,
__func__ , err , event - > hw . config_base ) ;
return err ;
}
static int cf_diag_event_init ( struct perf_event * event )
{
struct perf_event_attr * attr = & event - > attr ;
int err = - ENOENT ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
2019-11-15 12:50:46 +01:00
" %s event %p cpu %d config %#llx type:%u "
2018-10-29 13:16:38 +00:00
" sample_type %#llx cf_diag_events %d \n " , __func__ ,
2019-11-15 12:50:46 +01:00
event , event - > cpu , attr - > config , event - > pmu - > type ,
attr - > sample_type , atomic_read ( & cf_diag_events ) ) ;
2018-10-29 13:16:38 +00:00
if ( event - > attr . config ! = PERF_EVENT_CPUM_CF_DIAG | |
2019-11-15 12:50:46 +01:00
event - > attr . type ! = event - > pmu - > type )
2018-10-29 13:16:38 +00:00
goto out ;
/* Raw events are used to access counters directly,
* hence do not permit excludes .
* This event is usesless without PERF_SAMPLE_RAW to return counter set
* values as raw data .
*/
if ( attr - > exclude_kernel | | attr - > exclude_user | | attr - > exclude_hv | |
! ( attr - > sample_type & ( PERF_SAMPLE_CPU | PERF_SAMPLE_RAW ) ) ) {
err = - EOPNOTSUPP ;
goto out ;
}
/* Initialize for using the CPU-measurement counter facility */
if ( atomic_inc_return ( & cf_diag_events ) = = 1 ) {
if ( __kernel_cpumcf_begin ( ) ) {
atomic_dec ( & cf_diag_events ) ;
err = - EBUSY ;
goto out ;
}
}
event - > destroy = cf_diag_perf_event_destroy ;
err = __hw_perf_event_init ( event ) ;
if ( unlikely ( err ) )
event - > destroy ( event ) ;
out :
debug_sprintf_event ( cf_diag_dbg , 5 , " %s err %d \n " , __func__ , err ) ;
return err ;
}
static void cf_diag_read ( struct perf_event * event )
{
debug_sprintf_event ( cf_diag_dbg , 5 , " %s event %p \n " , __func__ , event ) ;
}
/* Return the maximum possible counter set size (in number of 8 byte counters)
* depending on type and model number .
*/
static size_t cf_diag_ctrset_size ( enum cpumf_ctr_set ctrset ,
struct cpumf_ctr_info * info )
{
size_t ctrset_size = 0 ;
switch ( ctrset ) {
case CPUMF_CTR_SET_BASIC :
if ( info - > cfvn > = 1 )
ctrset_size = 6 ;
break ;
case CPUMF_CTR_SET_USER :
if ( info - > cfvn = = 1 )
ctrset_size = 6 ;
else if ( info - > cfvn > = 3 )
ctrset_size = 2 ;
break ;
case CPUMF_CTR_SET_CRYPTO :
2019-04-23 11:36:27 +02:00
if ( info - > csvn > = 1 & & info - > csvn < = 5 )
ctrset_size = 16 ;
else if ( info - > csvn = = 6 )
ctrset_size = 20 ;
2018-10-29 13:16:38 +00:00
break ;
case CPUMF_CTR_SET_EXT :
if ( info - > csvn = = 1 )
ctrset_size = 32 ;
else if ( info - > csvn = = 2 )
ctrset_size = 48 ;
2019-04-23 11:36:27 +02:00
else if ( info - > csvn > = 3 & & info - > csvn < = 5 )
2018-10-29 13:16:38 +00:00
ctrset_size = 128 ;
2019-04-23 11:36:27 +02:00
else if ( info - > csvn = = 6 )
ctrset_size = 160 ;
2018-10-29 13:16:38 +00:00
break ;
case CPUMF_CTR_SET_MT_DIAG :
if ( info - > csvn > 3 )
ctrset_size = 48 ;
break ;
case CPUMF_CTR_SET_MAX :
break ;
}
return ctrset_size ;
}
/* Calculate memory needed to store all counter sets together with header and
* trailer data . This is independend of the counter set authorization which
* can vary depending on the configuration .
*/
static size_t cf_diag_ctrset_maxsize ( struct cpumf_ctr_info * info )
{
size_t max_size = sizeof ( struct cf_trailer_entry ) ;
enum cpumf_ctr_set i ;
for ( i = CPUMF_CTR_SET_BASIC ; i < CPUMF_CTR_SET_MAX ; + + i ) {
size_t size = cf_diag_ctrset_size ( i , info ) ;
if ( size )
max_size + = size * sizeof ( u64 ) +
sizeof ( struct cf_ctrset_entry ) ;
}
debug_sprintf_event ( cf_diag_dbg , 5 , " %s max_size %zu \n " , __func__ ,
max_size ) ;
return max_size ;
}
/* Read a counter set. The counter set number determines which counter set and
* the CPUM - CF first and second version number determine the number of
* available counters in this counter set .
* Each counter set starts with header containing the counter set number and
* the number of 8 byte counters .
*
* The functions returns the number of bytes occupied by this counter set
* including the header .
* If there is no counter in the counter set , this counter set is useless and
* zero is returned on this case .
*/
static size_t cf_diag_getctrset ( struct cf_ctrset_entry * ctrdata , int ctrset ,
size_t room )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
size_t ctrset_size , need = 0 ;
int rc = 3 ; /* Assume write failure */
ctrdata - > def = CF_DIAG_CTRSET_DEF ;
ctrdata - > set = ctrset ;
ctrdata - > res1 = 0 ;
ctrset_size = cf_diag_ctrset_size ( ctrset , & cpuhw - > info ) ;
if ( ctrset_size ) { /* Save data */
need = ctrset_size * sizeof ( u64 ) + sizeof ( * ctrdata ) ;
if ( need < = room )
rc = ctr_stcctm ( ctrset , ctrset_size ,
( u64 * ) ( ctrdata + 1 ) ) ;
if ( rc ! = 3 )
ctrdata - > ctr = ctrset_size ;
else
need = 0 ;
}
debug_sprintf_event ( cf_diag_dbg , 6 ,
" %s ctrset %d ctrset_size %zu cfvn %d csvn %d "
2019-09-23 07:56:47 +02:00
" need %zd rc %d \n " ,
2018-10-29 13:16:38 +00:00
__func__ , ctrset , ctrset_size , cpuhw - > info . cfvn ,
cpuhw - > info . csvn , need , rc ) ;
return need ;
}
/* Read out all counter sets and save them in the provided data buffer.
* The last 64 byte host an artificial trailer entry .
*/
static size_t cf_diag_getctr ( void * data , size_t sz , unsigned long auth )
{
struct cf_trailer_entry * trailer ;
size_t offset = 0 , done ;
int i ;
memset ( data , 0 , sz ) ;
sz - = sizeof ( * trailer ) ; /* Always room for trailer */
for ( i = CPUMF_CTR_SET_BASIC ; i < CPUMF_CTR_SET_MAX ; + + i ) {
struct cf_ctrset_entry * ctrdata = data + offset ;
if ( ! ( auth & cpumf_ctr_ctl [ i ] ) )
continue ; /* Counter set not authorized */
done = cf_diag_getctrset ( ctrdata , i , sz - offset ) ;
offset + = done ;
debug_sprintf_event ( cf_diag_dbg , 6 ,
" %s ctrset %d offset %zu done %zu \n " ,
__func__ , i , offset , done ) ;
}
trailer = data + offset ;
cf_diag_trailer ( trailer ) ;
return offset + sizeof ( * trailer ) ;
}
/* Calculate the difference for each counter in a counter set. */
static void cf_diag_diffctrset ( u64 * pstart , u64 * pstop , int counters )
{
for ( ; - - counters > = 0 ; + + pstart , + + pstop )
if ( * pstop > = * pstart )
* pstop - = * pstart ;
else
* pstop = * pstart - * pstop ;
}
/* Scan the counter sets and calculate the difference of each counter
* in each set . The result is the increment of each counter during the
* period the counter set has been activated .
*
* Return true on success .
*/
static int cf_diag_diffctr ( struct cf_diag_csd * csd , unsigned long auth )
{
struct cf_trailer_entry * trailer_start , * trailer_stop ;
struct cf_ctrset_entry * ctrstart , * ctrstop ;
size_t offset = 0 ;
auth & = ( 1 < < CPUMF_LCCTL_ENABLE_SHIFT ) - 1 ;
do {
ctrstart = ( struct cf_ctrset_entry * ) ( csd - > start + offset ) ;
ctrstop = ( struct cf_ctrset_entry * ) ( csd - > data + offset ) ;
if ( memcmp ( ctrstop , ctrstart , sizeof ( * ctrstop ) ) ) {
pr_err ( " cpum_cf_diag counter set compare error "
" in set %i \n " , ctrstart - > set ) ;
return 0 ;
}
auth & = ~ cpumf_ctr_ctl [ ctrstart - > set ] ;
if ( ctrstart - > def = = CF_DIAG_CTRSET_DEF ) {
cf_diag_diffctrset ( ( u64 * ) ( ctrstart + 1 ) ,
( u64 * ) ( ctrstop + 1 ) , ctrstart - > ctr ) ;
offset + = ctrstart - > ctr * sizeof ( u64 ) +
sizeof ( * ctrstart ) ;
}
debug_sprintf_event ( cf_diag_dbg , 6 ,
" %s set %d ctr %d offset %zu auth %lx \n " ,
__func__ , ctrstart - > set , ctrstart - > ctr ,
offset , auth ) ;
} while ( ctrstart - > def & & auth ) ;
/* Save time_stamp from start of event in stop's trailer */
trailer_start = ( struct cf_trailer_entry * ) ( csd - > start + offset ) ;
trailer_stop = ( struct cf_trailer_entry * ) ( csd - > data + offset ) ;
trailer_stop - > progusage [ 0 ] = trailer_start - > timestamp ;
return 1 ;
}
/* Create perf event sample with the counter sets as raw data. The sample
* is then pushed to the event subsystem and the function checks for
* possible event overflows . If an event overflow occurs , the PMU is
* stopped .
*
* Return non - zero if an event overflow occurred .
*/
static int cf_diag_push_sample ( struct perf_event * event ,
struct cf_diag_csd * csd )
{
struct perf_sample_data data ;
struct perf_raw_record raw ;
struct pt_regs regs ;
int overflow ;
/* Setup perf sample */
perf_sample_data_init ( & data , 0 , event - > hw . last_period ) ;
memset ( & regs , 0 , sizeof ( regs ) ) ;
memset ( & raw , 0 , sizeof ( raw ) ) ;
if ( event - > attr . sample_type & PERF_SAMPLE_CPU )
data . cpu_entry . cpu = event - > cpu ;
if ( event - > attr . sample_type & PERF_SAMPLE_RAW ) {
raw . frag . size = csd - > used ;
raw . frag . data = csd - > data ;
raw . size = csd - > used ;
data . raw = & raw ;
}
overflow = perf_event_overflow ( event , & data , & regs ) ;
debug_sprintf_event ( cf_diag_dbg , 6 ,
" %s event %p cpu %d sample_type %#llx raw %d "
" ov %d \n " , __func__ , event , event - > cpu ,
event - > attr . sample_type , raw . size , overflow ) ;
if ( overflow )
event - > pmu - > stop ( event , 0 ) ;
perf_event_update_userpage ( event ) ;
return overflow ;
}
static void cf_diag_start ( struct perf_event * event , int flags )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
struct cf_diag_csd * csd = this_cpu_ptr ( & cf_diag_csd ) ;
struct hw_perf_event * hwc = & event - > hw ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s event %p cpu %d flags %#x hwc-state %#x \n " ,
__func__ , event , event - > cpu , flags , hwc - > state ) ;
if ( WARN_ON_ONCE ( ! ( hwc - > state & PERF_HES_STOPPED ) ) )
return ;
/* (Re-)enable and activate all counter sets */
lcctl ( 0 ) ; /* Reset counter sets */
hwc - > state = 0 ;
ctr_set_multiple_enable ( & cpuhw - > state , hwc - > config_base ) ;
lcctl ( cpuhw - > state ) ; /* Enable counter sets */
csd - > used = cf_diag_getctr ( csd - > start , sizeof ( csd - > start ) ,
event - > hw . config_base ) ;
ctr_set_multiple_start ( & cpuhw - > state , hwc - > config_base ) ;
/* Function cf_diag_enable() starts the counter sets. */
}
static void cf_diag_stop ( struct perf_event * event , int flags )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
struct cf_diag_csd * csd = this_cpu_ptr ( & cf_diag_csd ) ;
struct hw_perf_event * hwc = & event - > hw ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s event %p cpu %d flags %#x hwc-state %#x \n " ,
__func__ , event , event - > cpu , flags , hwc - > state ) ;
/* Deactivate all counter sets */
ctr_set_multiple_stop ( & cpuhw - > state , hwc - > config_base ) ;
local64_inc ( & event - > count ) ;
csd - > used = cf_diag_getctr ( csd - > data , sizeof ( csd - > data ) ,
event - > hw . config_base ) ;
if ( cf_diag_diffctr ( csd , event - > hw . config_base ) )
cf_diag_push_sample ( event , csd ) ;
hwc - > state | = PERF_HES_STOPPED ;
}
static int cf_diag_add ( struct perf_event * event , int flags )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
int err = 0 ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
2019-09-23 07:56:47 +02:00
" %s event %p cpu %d flags %#x cpuhw %p \n " ,
2018-10-29 13:16:38 +00:00
__func__ , event , event - > cpu , flags , cpuhw ) ;
if ( cpuhw - > flags & PMU_F_IN_USE ) {
err = - EAGAIN ;
goto out ;
}
event - > hw . state = PERF_HES_UPTODATE | PERF_HES_STOPPED ;
cpuhw - > flags | = PMU_F_IN_USE ;
if ( flags & PERF_EF_START )
cf_diag_start ( event , PERF_EF_RELOAD ) ;
out :
debug_sprintf_event ( cf_diag_dbg , 5 , " %s err %d \n " , __func__ , err ) ;
return err ;
}
static void cf_diag_del ( struct perf_event * event , int flags )
{
struct cpu_cf_events * cpuhw = this_cpu_ptr ( & cpu_cf_events ) ;
debug_sprintf_event ( cf_diag_dbg , 5 ,
" %s event %p cpu %d flags %#x \n " ,
__func__ , event , event - > cpu , flags ) ;
cf_diag_stop ( event , PERF_EF_UPDATE ) ;
ctr_set_multiple_stop ( & cpuhw - > state , event - > hw . config_base ) ;
ctr_set_multiple_disable ( & cpuhw - > state , event - > hw . config_base ) ;
cpuhw - > flags & = ~ PMU_F_IN_USE ;
}
CPUMF_EVENT_ATTR ( CF_DIAG , CF_DIAG , PERF_EVENT_CPUM_CF_DIAG ) ;
static struct attribute * cf_diag_events_attr [ ] = {
CPUMF_EVENT_PTR ( CF_DIAG , CF_DIAG ) ,
NULL ,
} ;
PMU_FORMAT_ATTR ( event , " config:0-63 " ) ;
static struct attribute * cf_diag_format_attr [ ] = {
& format_attr_event . attr ,
NULL ,
} ;
static struct attribute_group cf_diag_events_group = {
. name = " events " ,
. attrs = cf_diag_events_attr ,
} ;
static struct attribute_group cf_diag_format_group = {
. name = " format " ,
. attrs = cf_diag_format_attr ,
} ;
static const struct attribute_group * cf_diag_attr_groups [ ] = {
& cf_diag_events_group ,
& cf_diag_format_group ,
NULL ,
} ;
/* Performance monitoring unit for s390x */
static struct pmu cf_diag = {
. task_ctx_nr = perf_sw_context ,
. pmu_enable = cf_diag_enable ,
. pmu_disable = cf_diag_disable ,
. event_init = cf_diag_event_init ,
. add = cf_diag_add ,
. del = cf_diag_del ,
. start = cf_diag_start ,
. stop = cf_diag_stop ,
. read = cf_diag_read ,
. attr_groups = cf_diag_attr_groups
} ;
/* Get the CPU speed, try sampling facility first and CPU attributes second. */
static void cf_diag_get_cpu_speed ( void )
{
if ( cpum_sf_avail ( ) ) { /* Sampling facility first */
struct hws_qsi_info_block si ;
memset ( & si , 0 , sizeof ( si ) ) ;
if ( ! qsi ( & si ) ) {
cf_diag_cpu_speed = si . cpu_speed ;
return ;
}
}
if ( test_facility ( 34 ) ) { /* CPU speed extract static part */
unsigned long mhz = __ecag ( ECAG_CPU_ATTRIBUTE , 0 ) ;
if ( mhz ! = - 1UL )
cf_diag_cpu_speed = mhz & 0xffffffff ;
}
}
/* Initialize the counter set PMU to generate complete counter set data as
* event raw data . This relies on the CPU Measurement Counter Facility device
* already being loaded and initialized .
*/
static int __init cf_diag_init ( void )
{
struct cpumf_ctr_info info ;
size_t need ;
int rc ;
if ( ! kernel_cpumcf_avail ( ) | | ! stccm_avail ( ) | | qctri ( & info ) )
return - ENODEV ;
cf_diag_get_cpu_speed ( ) ;
/* Make sure the counter set data fits into predefined buffer. */
need = cf_diag_ctrset_maxsize ( & info ) ;
if ( need > sizeof ( ( ( struct cf_diag_csd * ) 0 ) - > start ) ) {
pr_err ( " Insufficient memory for PMU(cpum_cf_diag) need=%zu \n " ,
need ) ;
return - ENOMEM ;
}
/* Setup s390dbf facility */
cf_diag_dbg = debug_register ( KMSG_COMPONENT , 2 , 1 , 128 ) ;
if ( ! cf_diag_dbg ) {
pr_err ( " Registration of s390dbf(cpum_cf_diag) failed \n " ) ;
return - ENOMEM ;
}
debug_register_view ( cf_diag_dbg , & debug_sprintf_view ) ;
2019-11-15 12:50:46 +01:00
rc = perf_pmu_register ( & cf_diag , " cpum_cf_diag " , - 1 ) ;
2018-10-29 13:16:38 +00:00
if ( rc ) {
debug_unregister_view ( cf_diag_dbg , & debug_sprintf_view ) ;
debug_unregister ( cf_diag_dbg ) ;
pr_err ( " Registration of PMU(cpum_cf_diag) failed with rc=%i \n " ,
rc ) ;
}
return rc ;
}
arch_initcall ( cf_diag_init ) ;