2012-03-15 23:09:17 +04:00
# include <linux/list.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <stdio.h>
# include <dirent.h>
# include "sysfs.h"
# include "util.h"
# include "pmu.h"
# include "parse-events.h"
2012-09-10 11:53:50 +04:00
# include "cpumap.h"
2012-03-15 23:09:17 +04:00
2012-08-16 23:10:24 +04:00
# define EVENT_SOURCE_DEVICE_PATH " / bus / event_source / devices / "
2012-03-15 23:09:17 +04:00
int perf_pmu_parse ( struct list_head * list , char * name ) ;
extern FILE * perf_pmu_in ;
static LIST_HEAD ( pmus ) ;
/*
* Parse & process all the sysfs attributes located under
* the directory specified in ' dir ' parameter .
*/
2012-11-10 04:46:50 +04:00
int perf_pmu__format_parse ( char * dir , struct list_head * head )
2012-03-15 23:09:17 +04:00
{
struct dirent * evt_ent ;
DIR * format_dir ;
int ret = 0 ;
format_dir = opendir ( dir ) ;
if ( ! format_dir )
return - EINVAL ;
while ( ! ret & & ( evt_ent = readdir ( format_dir ) ) ) {
char path [ PATH_MAX ] ;
char * name = evt_ent - > d_name ;
FILE * file ;
if ( ! strcmp ( name , " . " ) | | ! strcmp ( name , " .. " ) )
continue ;
snprintf ( path , PATH_MAX , " %s/%s " , dir , name ) ;
ret = - EINVAL ;
file = fopen ( path , " r " ) ;
if ( ! file )
break ;
perf_pmu_in = file ;
ret = perf_pmu_parse ( head , name ) ;
fclose ( file ) ;
}
closedir ( format_dir ) ;
return ret ;
}
/*
* Reading / parsing the default pmu format definition , which should be
* located at :
* / sys / bus / event_source / devices / < dev > / format as sysfs group attributes .
*/
static int pmu_format ( char * name , struct list_head * format )
{
struct stat st ;
char path [ PATH_MAX ] ;
const char * sysfs ;
sysfs = sysfs_find_mountpoint ( ) ;
if ( ! sysfs )
return - 1 ;
snprintf ( path , PATH_MAX ,
2012-08-16 23:10:24 +04:00
" %s " EVENT_SOURCE_DEVICE_PATH " %s/format " , sysfs , name ) ;
2012-03-15 23:09:17 +04:00
if ( stat ( path , & st ) < 0 )
2012-06-15 00:38:37 +04:00
return 0 ; /* no error if format does not exist */
2012-03-15 23:09:17 +04:00
2012-11-10 04:46:50 +04:00
if ( perf_pmu__format_parse ( path , format ) )
2012-03-15 23:09:17 +04:00
return - 1 ;
return 0 ;
}
2012-06-15 10:31:41 +04:00
static int perf_pmu__new_alias ( struct list_head * list , char * name , FILE * file )
{
2013-01-18 23:54:00 +04:00
struct perf_pmu_alias * alias ;
2012-06-15 10:31:41 +04:00
char buf [ 256 ] ;
int ret ;
ret = fread ( buf , 1 , sizeof ( buf ) , file ) ;
if ( ret = = 0 )
return - EINVAL ;
buf [ ret ] = 0 ;
alias = malloc ( sizeof ( * alias ) ) ;
if ( ! alias )
return - ENOMEM ;
INIT_LIST_HEAD ( & alias - > terms ) ;
ret = parse_events_terms ( & alias - > terms , buf ) ;
if ( ret ) {
free ( alias ) ;
return ret ;
}
alias - > name = strdup ( name ) ;
list_add_tail ( & alias - > list , list ) ;
return 0 ;
}
/*
* Process all the sysfs attributes located under the directory
* specified in ' dir ' parameter .
*/
static int pmu_aliases_parse ( char * dir , struct list_head * head )
{
struct dirent * evt_ent ;
DIR * event_dir ;
int ret = 0 ;
event_dir = opendir ( dir ) ;
if ( ! event_dir )
return - EINVAL ;
while ( ! ret & & ( evt_ent = readdir ( event_dir ) ) ) {
char path [ PATH_MAX ] ;
char * name = evt_ent - > d_name ;
FILE * file ;
if ( ! strcmp ( name , " . " ) | | ! strcmp ( name , " .. " ) )
continue ;
snprintf ( path , PATH_MAX , " %s/%s " , dir , name ) ;
ret = - EINVAL ;
file = fopen ( path , " r " ) ;
if ( ! file )
break ;
ret = perf_pmu__new_alias ( head , name , file ) ;
fclose ( file ) ;
}
closedir ( event_dir ) ;
return ret ;
}
/*
* Reading the pmu event aliases definition , which should be located at :
* / sys / bus / event_source / devices / < dev > / events as sysfs group attributes .
*/
static int pmu_aliases ( char * name , struct list_head * head )
{
struct stat st ;
char path [ PATH_MAX ] ;
const char * sysfs ;
sysfs = sysfs_find_mountpoint ( ) ;
if ( ! sysfs )
return - 1 ;
snprintf ( path , PATH_MAX ,
" %s/bus/event_source/devices/%s/events " , sysfs , name ) ;
if ( stat ( path , & st ) < 0 )
2012-10-10 16:53:16 +04:00
return 0 ; /* no error if 'events' does not exist */
2012-06-15 10:31:41 +04:00
if ( pmu_aliases_parse ( path , head ) )
return - 1 ;
return 0 ;
}
2013-01-18 23:54:00 +04:00
static int pmu_alias_terms ( struct perf_pmu_alias * alias ,
2012-06-15 10:31:41 +04:00
struct list_head * terms )
{
2013-01-18 23:29:49 +04:00
struct parse_events_term * term , * clone ;
2012-06-15 10:31:41 +04:00
LIST_HEAD ( list ) ;
int ret ;
list_for_each_entry ( term , & alias - > terms , list ) {
2013-01-18 23:29:49 +04:00
ret = parse_events_term__clone ( & clone , term ) ;
2012-06-15 10:31:41 +04:00
if ( ret ) {
parse_events__free_terms ( & list ) ;
return ret ;
}
list_add_tail ( & clone - > list , & list ) ;
}
list_splice ( & list , terms ) ;
return 0 ;
}
2012-03-15 23:09:17 +04:00
/*
* Reading / parsing the default pmu type value , which should be
* located at :
* / sys / bus / event_source / devices / < dev > / type as sysfs attribute .
*/
static int pmu_type ( char * name , __u32 * type )
{
struct stat st ;
char path [ PATH_MAX ] ;
const char * sysfs ;
FILE * file ;
int ret = 0 ;
sysfs = sysfs_find_mountpoint ( ) ;
if ( ! sysfs )
return - 1 ;
snprintf ( path , PATH_MAX ,
2012-08-16 23:10:24 +04:00
" %s " EVENT_SOURCE_DEVICE_PATH " %s/type " , sysfs , name ) ;
2012-03-15 23:09:17 +04:00
if ( stat ( path , & st ) < 0 )
return - 1 ;
file = fopen ( path , " r " ) ;
if ( ! file )
return - EINVAL ;
if ( 1 ! = fscanf ( file , " %u " , type ) )
ret = - 1 ;
fclose ( file ) ;
return ret ;
}
2012-08-16 23:10:24 +04:00
/* Add all pmus in sysfs to pmu list: */
static void pmu_read_sysfs ( void )
{
char path [ PATH_MAX ] ;
const char * sysfs ;
DIR * dir ;
struct dirent * dent ;
sysfs = sysfs_find_mountpoint ( ) ;
if ( ! sysfs )
return ;
snprintf ( path , PATH_MAX ,
" %s " EVENT_SOURCE_DEVICE_PATH , sysfs ) ;
dir = opendir ( path ) ;
if ( ! dir )
return ;
while ( ( dent = readdir ( dir ) ) ) {
if ( ! strcmp ( dent - > d_name , " . " ) | | ! strcmp ( dent - > d_name , " .. " ) )
continue ;
/* add to static LIST_HEAD(pmus): */
perf_pmu__find ( dent - > d_name ) ;
}
closedir ( dir ) ;
}
2012-09-10 11:53:50 +04:00
static struct cpu_map * pmu_cpumask ( char * name )
{
struct stat st ;
char path [ PATH_MAX ] ;
const char * sysfs ;
FILE * file ;
struct cpu_map * cpus ;
sysfs = sysfs_find_mountpoint ( ) ;
if ( ! sysfs )
return NULL ;
snprintf ( path , PATH_MAX ,
" %s/bus/event_source/devices/%s/cpumask " , sysfs , name ) ;
if ( stat ( path , & st ) < 0 )
return NULL ;
file = fopen ( path , " r " ) ;
if ( ! file )
return NULL ;
cpus = cpu_map__read ( file ) ;
fclose ( file ) ;
return cpus ;
}
2012-03-15 23:09:17 +04:00
static struct perf_pmu * pmu_lookup ( char * name )
{
struct perf_pmu * pmu ;
LIST_HEAD ( format ) ;
2012-06-15 10:31:41 +04:00
LIST_HEAD ( aliases ) ;
2012-03-15 23:09:17 +04:00
__u32 type ;
/*
* The pmu data we store & need consists of the pmu
* type value and format definitions . Load both right
* now .
*/
if ( pmu_format ( name , & format ) )
return NULL ;
2012-10-10 16:53:16 +04:00
if ( pmu_aliases ( name , & aliases ) )
return NULL ;
2012-03-15 23:09:17 +04:00
if ( pmu_type ( name , & type ) )
return NULL ;
pmu = zalloc ( sizeof ( * pmu ) ) ;
if ( ! pmu )
return NULL ;
2012-09-10 11:53:50 +04:00
pmu - > cpus = pmu_cpumask ( name ) ;
2012-03-15 23:09:17 +04:00
INIT_LIST_HEAD ( & pmu - > format ) ;
2012-06-15 10:31:41 +04:00
INIT_LIST_HEAD ( & pmu - > aliases ) ;
2012-03-15 23:09:17 +04:00
list_splice ( & format , & pmu - > format ) ;
2012-06-15 10:31:41 +04:00
list_splice ( & aliases , & pmu - > aliases ) ;
2012-03-15 23:09:17 +04:00
pmu - > name = strdup ( name ) ;
pmu - > type = type ;
2012-06-15 00:38:37 +04:00
list_add_tail ( & pmu - > list , & pmus ) ;
2012-03-15 23:09:17 +04:00
return pmu ;
}
static struct perf_pmu * pmu_find ( char * name )
{
struct perf_pmu * pmu ;
list_for_each_entry ( pmu , & pmus , list )
if ( ! strcmp ( pmu - > name , name ) )
return pmu ;
return NULL ;
}
2012-08-16 23:10:24 +04:00
struct perf_pmu * perf_pmu__scan ( struct perf_pmu * pmu )
{
/*
* pmu iterator : If pmu is NULL , we start at the begin ,
* otherwise return the next pmu . Returns NULL on end .
*/
if ( ! pmu ) {
pmu_read_sysfs ( ) ;
pmu = list_prepare_entry ( pmu , & pmus , list ) ;
}
list_for_each_entry_continue ( pmu , & pmus , list )
return pmu ;
return NULL ;
}
2012-03-15 23:09:17 +04:00
struct perf_pmu * perf_pmu__find ( char * name )
{
struct perf_pmu * pmu ;
/*
* Once PMU is loaded it stays in the list ,
* so we keep us from multiple reading / parsing
* the pmu format definitions .
*/
pmu = pmu_find ( name ) ;
if ( pmu )
return pmu ;
return pmu_lookup ( name ) ;
}
2013-01-18 23:54:00 +04:00
static struct perf_pmu_format *
2012-03-15 23:09:17 +04:00
pmu_find_format ( struct list_head * formats , char * name )
{
2013-01-18 23:54:00 +04:00
struct perf_pmu_format * format ;
2012-03-15 23:09:17 +04:00
list_for_each_entry ( format , formats , list )
if ( ! strcmp ( format - > name , name ) )
return format ;
return NULL ;
}
/*
* Returns value based on the format definition ( format parameter )
* and unformated value ( value parameter ) .
*
* TODO maybe optimize a little ; )
*/
static __u64 pmu_format_value ( unsigned long * format , __u64 value )
{
unsigned long fbit , vbit ;
__u64 v = 0 ;
for ( fbit = 0 , vbit = 0 ; fbit < PERF_PMU_FORMAT_BITS ; fbit + + ) {
if ( ! test_bit ( fbit , format ) )
continue ;
if ( ! ( value & ( 1llu < < vbit + + ) ) )
continue ;
v | = ( 1llu < < fbit ) ;
}
return v ;
}
/*
* Setup one of config [ 12 ] attr members based on the
* user input data - temr parameter .
*/
static int pmu_config_term ( struct list_head * formats ,
struct perf_event_attr * attr ,
2013-01-18 23:29:49 +04:00
struct parse_events_term * term )
2012-03-15 23:09:17 +04:00
{
2013-01-18 23:54:00 +04:00
struct perf_pmu_format * format ;
2012-03-15 23:09:17 +04:00
__u64 * vp ;
/*
* Support only for hardcoded and numnerial terms .
* Hardcoded terms should be already in , so nothing
* to be done for them .
*/
if ( parse_events__is_hardcoded_term ( term ) )
return 0 ;
2012-04-25 20:24:57 +04:00
if ( term - > type_val ! = PARSE_EVENTS__TERM_TYPE_NUM )
2012-03-15 23:09:17 +04:00
return - EINVAL ;
format = pmu_find_format ( formats , term - > config ) ;
if ( ! format )
return - EINVAL ;
switch ( format - > value ) {
case PERF_PMU_FORMAT_VALUE_CONFIG :
vp = & attr - > config ;
break ;
case PERF_PMU_FORMAT_VALUE_CONFIG1 :
vp = & attr - > config1 ;
break ;
case PERF_PMU_FORMAT_VALUE_CONFIG2 :
vp = & attr - > config2 ;
break ;
default :
return - EINVAL ;
}
2012-04-25 20:24:57 +04:00
/*
* XXX If we ever decide to go with string values for
* non - hardcoded terms , here ' s the place to translate
* them into value .
*/
2012-03-15 23:09:17 +04:00
* vp | = pmu_format_value ( format - > bits , term - > val . num ) ;
return 0 ;
}
2012-11-10 04:46:50 +04:00
int perf_pmu__config_terms ( struct list_head * formats ,
struct perf_event_attr * attr ,
struct list_head * head_terms )
2012-03-15 23:09:17 +04:00
{
2013-01-18 23:29:49 +04:00
struct parse_events_term * term ;
2012-03-15 23:09:17 +04:00
2012-05-21 11:12:53 +04:00
list_for_each_entry ( term , head_terms , list )
2012-03-15 23:09:17 +04:00
if ( pmu_config_term ( formats , attr , term ) )
return - EINVAL ;
return 0 ;
}
/*
* Configures event ' s ' attr ' parameter based on the :
* 1 ) users input - specified in terms parameter
* 2 ) pmu format definitions - specified by pmu parameter
*/
int perf_pmu__config ( struct perf_pmu * pmu , struct perf_event_attr * attr ,
struct list_head * head_terms )
{
attr - > type = pmu - > type ;
2012-11-10 04:46:50 +04:00
return perf_pmu__config_terms ( & pmu - > format , attr , head_terms ) ;
2012-03-15 23:09:17 +04:00
}
2013-01-18 23:54:00 +04:00
static struct perf_pmu_alias * pmu_find_alias ( struct perf_pmu * pmu ,
struct parse_events_term * term )
2012-06-15 10:31:41 +04:00
{
2013-01-18 23:54:00 +04:00
struct perf_pmu_alias * alias ;
2012-06-15 10:31:41 +04:00
char * name ;
if ( parse_events__is_hardcoded_term ( term ) )
return NULL ;
if ( term - > type_val = = PARSE_EVENTS__TERM_TYPE_NUM ) {
if ( term - > val . num ! = 1 )
return NULL ;
if ( pmu_find_format ( & pmu - > format , term - > config ) )
return NULL ;
name = term - > config ;
} else if ( term - > type_val = = PARSE_EVENTS__TERM_TYPE_STR ) {
if ( strcasecmp ( term - > config , " event " ) )
return NULL ;
name = term - > val . str ;
} else {
return NULL ;
}
list_for_each_entry ( alias , & pmu - > aliases , list ) {
if ( ! strcasecmp ( alias - > name , name ) )
return alias ;
}
return NULL ;
}
/*
* Find alias in the terms list and replace it with the terms
* defined for the alias
*/
int perf_pmu__check_alias ( struct perf_pmu * pmu , struct list_head * head_terms )
{
2013-01-18 23:29:49 +04:00
struct parse_events_term * term , * h ;
2013-01-18 23:54:00 +04:00
struct perf_pmu_alias * alias ;
2012-06-15 10:31:41 +04:00
int ret ;
list_for_each_entry_safe ( term , h , head_terms , list ) {
alias = pmu_find_alias ( pmu , term ) ;
if ( ! alias )
continue ;
ret = pmu_alias_terms ( alias , & term - > list ) ;
if ( ret )
return ret ;
list_del ( & term - > list ) ;
free ( term ) ;
}
return 0 ;
}
2012-03-15 23:09:17 +04:00
int perf_pmu__new_format ( struct list_head * list , char * name ,
int config , unsigned long * bits )
{
2013-01-18 23:54:00 +04:00
struct perf_pmu_format * format ;
2012-03-15 23:09:17 +04:00
format = zalloc ( sizeof ( * format ) ) ;
if ( ! format )
return - ENOMEM ;
format - > name = strdup ( name ) ;
format - > value = config ;
memcpy ( format - > bits , bits , sizeof ( format - > bits ) ) ;
list_add_tail ( & format - > list , list ) ;
return 0 ;
}
void perf_pmu__set_format ( unsigned long * bits , long from , long to )
{
long b ;
if ( ! to )
to = from ;
2013-01-17 21:11:30 +04:00
memset ( bits , 0 , BITS_TO_BYTES ( PERF_PMU_FORMAT_BITS ) ) ;
2012-03-15 23:09:17 +04:00
for ( b = from ; b < = to ; b + + )
set_bit ( b , bits ) ;
}