2012-07-20 11:15:04 +02:00
/*
2005-04-16 15:20:36 -07:00
* S390 Version
2012-07-20 11:15:04 +02:00
* Copyright IBM Corp . 2002 , 2011
2005-04-16 15:20:36 -07:00
* Author ( s ) : Thomas Spatzier ( tspat @ de . ibm . com )
2011-02-15 13:02:14 -05:00
* Author ( s ) : Mahesh Salgaonkar ( mahesh @ linux . vnet . ibm . com )
* Author ( s ) : Heinz Graalfs ( graalfs @ linux . vnet . ibm . com )
2011-11-25 20:03:05 +01:00
* Author ( s ) : Andreas Krebbel ( krebbel @ linux . vnet . ibm . com )
2005-04-16 15:20:36 -07:00
*
2011-02-15 13:02:14 -05:00
* @ remark Copyright 2002 - 2011 OProfile authors
2005-04-16 15:20:36 -07:00
*/
# include <linux/oprofile.h>
2013-12-12 16:52:48 +01:00
# include <linux/perf_event.h>
2005-04-16 15:20:36 -07:00
# include <linux/init.h>
# include <linux/errno.h>
2011-02-15 13:02:14 -05:00
# include <linux/fs.h>
2011-11-25 20:03:05 +01:00
# include <linux/module.h>
# include <asm/processor.h>
2011-02-15 13:02:14 -05:00
# include "../../../drivers/oprofile/oprof.h"
2011-03-23 10:15:00 +01:00
extern void s390_backtrace ( struct pt_regs * const regs , unsigned int depth ) ;
# ifdef CONFIG_64BIT
2011-02-15 13:02:14 -05:00
# include "hwsampler.h"
2011-11-25 20:03:05 +01:00
# include "op_counter.h"
2011-02-15 13:02:14 -05:00
2011-06-22 16:24:09 +02:00
# define DEFAULT_INTERVAL 4127518
2011-02-15 13:02:14 -05:00
# define DEFAULT_SDBT_BLOCKS 1
# define DEFAULT_SDB_BLOCKS 511
static unsigned long oprofile_hw_interval = DEFAULT_INTERVAL ;
static unsigned long oprofile_min_interval ;
static unsigned long oprofile_max_interval ;
static unsigned long oprofile_sdbt_blocks = DEFAULT_SDBT_BLOCKS ;
static unsigned long oprofile_sdb_blocks = DEFAULT_SDB_BLOCKS ;
2005-04-16 15:20:36 -07:00
2011-11-25 20:03:05 +01:00
static int hwsampler_enabled ;
2011-02-15 13:02:14 -05:00
static int hwsampler_running ; /* start_mutex must be held to change */
2011-11-25 20:03:05 +01:00
static int hwsampler_available ;
2011-02-15 13:02:14 -05:00
static struct oprofile_operations timer_ops ;
2006-01-06 00:19:16 -08:00
2011-11-25 20:03:05 +01:00
struct op_counter_config counter_config ;
enum __force_cpu_type {
reserved = 0 , /* do not force */
timer ,
} ;
static int force_cpu_type ;
static int set_cpu_type ( const char * str , struct kernel_param * kp )
{
if ( ! strcmp ( str , " timer " ) ) {
force_cpu_type = timer ;
printk ( KERN_INFO " oprofile: forcing timer to be returned "
" as cpu type \n " ) ;
} else {
force_cpu_type = 0 ;
}
return 0 ;
}
module_param_call ( cpu_type , set_cpu_type , NULL , NULL , 0 ) ;
MODULE_PARM_DESC ( cpu_type , " Force legacy basic mode sampling "
" (report cpu_type \" timer \" " ) ;
2013-12-12 16:52:48 +01:00
static int __oprofile_hwsampler_start ( void )
{
int retval ;
retval = hwsampler_allocate ( oprofile_sdbt_blocks , oprofile_sdb_blocks ) ;
if ( retval )
return retval ;
retval = hwsampler_start_all ( oprofile_hw_interval ) ;
if ( retval )
hwsampler_deallocate ( ) ;
return retval ;
}
2011-02-15 13:02:14 -05:00
static int oprofile_hwsampler_start ( void )
{
int retval ;
2011-11-25 20:03:05 +01:00
hwsampler_running = hwsampler_enabled ;
2011-02-15 13:02:14 -05:00
if ( ! hwsampler_running )
return timer_ops . start ( ) ;
2013-12-12 16:52:48 +01:00
retval = perf_reserve_sampling ( ) ;
2011-02-15 13:02:14 -05:00
if ( retval )
return retval ;
2013-12-12 16:52:48 +01:00
retval = __oprofile_hwsampler_start ( ) ;
2011-02-15 13:02:14 -05:00
if ( retval )
2013-12-12 16:52:48 +01:00
perf_release_sampling ( ) ;
2011-02-15 13:02:14 -05:00
return retval ;
}
static void oprofile_hwsampler_stop ( void )
{
if ( ! hwsampler_running ) {
timer_ops . stop ( ) ;
return ;
}
hwsampler_stop_all ( ) ;
hwsampler_deallocate ( ) ;
2013-12-12 16:52:48 +01:00
perf_release_sampling ( ) ;
2011-02-15 13:02:14 -05:00
return ;
}
2011-11-25 20:03:05 +01:00
/*
* File ops used for :
* / dev / oprofile / 0 / enabled
* / dev / oprofile / hwsampling / hwsampler ( cpu_type = timer )
*/
2011-02-15 13:02:14 -05:00
static ssize_t hwsampler_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
2011-11-25 20:03:05 +01:00
return oprofilefs_ulong_to_user ( hwsampler_enabled , buf , count , offset ) ;
2011-02-15 13:02:14 -05:00
}
static ssize_t hwsampler_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2011-12-19 16:38:30 +01:00
if ( retval < = 0 )
2011-02-15 13:02:14 -05:00
return retval ;
2011-11-25 20:03:05 +01:00
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
2011-02-15 13:02:14 -05:00
if ( oprofile_started )
/*
* save to do without locking as we set
* hwsampler_running in start ( ) when start_mutex is
* held
*/
return - EBUSY ;
2011-11-25 20:03:05 +01:00
hwsampler_enabled = val ;
2011-02-15 13:02:14 -05:00
return count ;
}
static const struct file_operations hwsampler_fops = {
. read = hwsampler_read ,
. write = hwsampler_write ,
} ;
2011-11-25 20:03:05 +01:00
/*
* File ops used for :
* / dev / oprofile / 0 / count
* / dev / oprofile / hwsampling / hw_interval ( cpu_type = timer )
*
* Make sure that the value is within the hardware range .
*/
static ssize_t hw_interval_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
return oprofilefs_ulong_to_user ( oprofile_hw_interval , buf ,
count , offset ) ;
}
static ssize_t hw_interval_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2012-07-19 18:28:26 +02:00
if ( retval < = 0 )
2011-11-25 20:03:05 +01:00
return retval ;
if ( val < oprofile_min_interval )
oprofile_hw_interval = oprofile_min_interval ;
else if ( val > oprofile_max_interval )
oprofile_hw_interval = oprofile_max_interval ;
else
oprofile_hw_interval = val ;
return count ;
}
static const struct file_operations hw_interval_fops = {
. read = hw_interval_read ,
. write = hw_interval_write ,
} ;
/*
* File ops used for :
* / dev / oprofile / 0 / event
* Only a single event with number 0 is supported with this counter .
*
* / dev / oprofile / 0 / unit_mask
* This is a dummy file needed by the user space tools .
* No value other than 0 is accepted or returned .
*/
static ssize_t hwsampler_zero_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
return oprofilefs_ulong_to_user ( 0 , buf , count , offset ) ;
}
static ssize_t hwsampler_zero_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2012-07-19 18:28:26 +02:00
if ( retval < = 0 )
2011-11-25 20:03:05 +01:00
return retval ;
if ( val ! = 0 )
return - EINVAL ;
return count ;
}
static const struct file_operations zero_fops = {
. read = hwsampler_zero_read ,
. write = hwsampler_zero_write ,
} ;
/* /dev/oprofile/0/kernel file ops. */
static ssize_t hwsampler_kernel_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
return oprofilefs_ulong_to_user ( counter_config . kernel ,
buf , count , offset ) ;
}
static ssize_t hwsampler_kernel_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2012-07-19 18:28:26 +02:00
if ( retval < = 0 )
2011-11-25 20:03:05 +01:00
return retval ;
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
counter_config . kernel = val ;
return count ;
}
static const struct file_operations kernel_fops = {
. read = hwsampler_kernel_read ,
. write = hwsampler_kernel_write ,
} ;
/* /dev/oprofile/0/user file ops. */
static ssize_t hwsampler_user_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
return oprofilefs_ulong_to_user ( counter_config . user ,
buf , count , offset ) ;
}
static ssize_t hwsampler_user_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2012-07-19 18:28:26 +02:00
if ( retval < = 0 )
2011-11-25 20:03:05 +01:00
return retval ;
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
counter_config . user = val ;
return count ;
}
static const struct file_operations user_fops = {
. read = hwsampler_user_read ,
. write = hwsampler_user_write ,
} ;
/*
* File ops used for : / dev / oprofile / timer / enabled
* The value always has to be the inverted value of hwsampler_enabled . So
* no separate variable is created . That way we do not need locking .
*/
static ssize_t timer_enabled_read ( struct file * file , char __user * buf ,
size_t count , loff_t * offset )
{
return oprofilefs_ulong_to_user ( ! hwsampler_enabled , buf , count , offset ) ;
}
static ssize_t timer_enabled_write ( struct file * file , char const __user * buf ,
size_t count , loff_t * offset )
{
unsigned long val ;
int retval ;
if ( * offset )
return - EINVAL ;
retval = oprofilefs_ulong_from_user ( & val , buf , count ) ;
2012-07-19 18:28:26 +02:00
if ( retval < = 0 )
2011-11-25 20:03:05 +01:00
return retval ;
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
/* Timer cannot be disabled without having hardware sampling. */
if ( val = = 0 & & ! hwsampler_available )
return - EINVAL ;
if ( oprofile_started )
/*
* save to do without locking as we set
* hwsampler_running in start ( ) when start_mutex is
* held
*/
return - EBUSY ;
hwsampler_enabled = ! val ;
return count ;
}
static const struct file_operations timer_enabled_fops = {
. read = timer_enabled_read ,
. write = timer_enabled_write ,
} ;
2013-07-19 15:52:42 +04:00
static int oprofile_create_hwsampling_files ( struct dentry * root )
2011-02-15 13:02:14 -05:00
{
2011-11-25 20:03:05 +01:00
struct dentry * dir ;
2013-07-19 15:58:27 +04:00
dir = oprofilefs_mkdir ( root , " timer " ) ;
2011-11-25 20:03:05 +01:00
if ( ! dir )
return - EINVAL ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_file ( dir , " enabled " , & timer_enabled_fops ) ;
2011-11-25 20:03:05 +01:00
if ( ! hwsampler_available )
return 0 ;
2011-02-15 13:02:14 -05:00
/* reinitialize default values */
2011-11-25 20:03:05 +01:00
hwsampler_enabled = 1 ;
counter_config . kernel = 1 ;
counter_config . user = 1 ;
2011-02-15 13:02:14 -05:00
2011-11-25 20:03:05 +01:00
if ( ! force_cpu_type ) {
/*
* Create the counter file system . A single virtual
* counter is created which can be used to
* enable / disable hardware sampling dynamically from
* user space . The user space will configure a single
* counter with a single event . The value of ' event '
* and ' unit_mask ' are not evaluated by the kernel code
* and can only be set to 0.
*/
2013-07-19 15:58:27 +04:00
dir = oprofilefs_mkdir ( root , " 0 " ) ;
2011-11-25 20:03:05 +01:00
if ( ! dir )
return - EINVAL ;
2011-02-15 13:02:14 -05:00
2013-07-19 16:10:36 +04:00
oprofilefs_create_file ( dir , " enabled " , & hwsampler_fops ) ;
oprofilefs_create_file ( dir , " event " , & zero_fops ) ;
oprofilefs_create_file ( dir , " count " , & hw_interval_fops ) ;
oprofilefs_create_file ( dir , " unit_mask " , & zero_fops ) ;
oprofilefs_create_file ( dir , " kernel " , & kernel_fops ) ;
oprofilefs_create_file ( dir , " user " , & user_fops ) ;
oprofilefs_create_ulong ( dir , " hw_sdbt_blocks " ,
2011-11-25 20:03:05 +01:00
& oprofile_sdbt_blocks ) ;
2011-02-15 13:02:14 -05:00
2011-11-25 20:03:05 +01:00
} else {
/*
* Hardware sampling can be used but the cpu_type is
* forced to timer in order to deal with legacy user
* space tools . The / dev / oprofile / hwsampling fs is
* provided in that case .
*/
2013-07-19 15:58:27 +04:00
dir = oprofilefs_mkdir ( root , " hwsampling " ) ;
2011-11-25 20:03:05 +01:00
if ( ! dir )
return - EINVAL ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_file ( dir , " hwsampler " ,
2011-11-25 20:03:05 +01:00
& hwsampler_fops ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_file ( dir , " hw_interval " ,
2011-11-25 20:03:05 +01:00
& hw_interval_fops ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ro_ulong ( dir , " hw_min_interval " ,
2011-11-25 20:03:05 +01:00
& oprofile_min_interval ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ro_ulong ( dir , " hw_max_interval " ,
2011-11-25 20:03:05 +01:00
& oprofile_max_interval ) ;
2013-07-19 16:10:36 +04:00
oprofilefs_create_ulong ( dir , " hw_sdbt_blocks " ,
2011-11-25 20:03:05 +01:00
& oprofile_sdbt_blocks ) ;
}
2011-02-15 13:02:14 -05:00
return 0 ;
}
2011-03-16 12:10:12 +01:00
static int oprofile_hwsampler_init ( struct oprofile_operations * ops )
2011-02-15 13:02:14 -05:00
{
2011-11-25 20:03:05 +01:00
/*
* Initialize the timer mode infrastructure as well in order
* to be able to switch back dynamically . oprofile_timer_init
* is not supposed to fail .
*/
if ( oprofile_timer_init ( ops ) )
BUG ( ) ;
memcpy ( & timer_ops , ops , sizeof ( timer_ops ) ) ;
ops - > create_files = oprofile_create_hwsampling_files ;
/*
* If the user space tools do not support newer cpu types ,
* the force_cpu_type module parameter
* can be used to always return \ " timer \" as cpu type.
*/
if ( force_cpu_type ! = timer ) {
struct cpuid id ;
get_cpu_id ( & id ) ;
switch ( id . machine ) {
case 0x2097 : case 0x2098 : ops - > cpu_type = " s390/z10 " ; break ;
case 0x2817 : case 0x2818 : ops - > cpu_type = " s390/z196 " ; break ;
2013-07-24 10:35:33 +02:00
case 0x2827 : case 0x2828 : ops - > cpu_type = " s390/zEC12 " ; break ;
2011-11-25 20:03:05 +01:00
default : return - ENODEV ;
}
}
2011-02-15 13:02:14 -05:00
if ( hwsampler_setup ( ) )
return - ENODEV ;
/*
2011-11-25 20:03:05 +01:00
* Query the range for the sampling interval from the
* hardware .
2011-02-15 13:02:14 -05:00
*/
oprofile_min_interval = hwsampler_query_min_interval ( ) ;
2011-05-10 17:13:42 +02:00
if ( oprofile_min_interval = = 0 )
2011-02-15 13:02:14 -05:00
return - ENODEV ;
oprofile_max_interval = hwsampler_query_max_interval ( ) ;
2011-05-10 17:13:42 +02:00
if ( oprofile_max_interval = = 0 )
2011-02-15 13:02:14 -05:00
return - ENODEV ;
2011-06-22 16:24:08 +02:00
/* The initial value should be sane */
if ( oprofile_hw_interval < oprofile_min_interval )
oprofile_hw_interval = oprofile_min_interval ;
if ( oprofile_hw_interval > oprofile_max_interval )
oprofile_hw_interval = oprofile_max_interval ;
2011-11-25 20:03:05 +01:00
printk ( KERN_INFO " oprofile: System z hardware sampling "
" facility found. \n " ) ;
2011-02-15 13:02:14 -05:00
ops - > start = oprofile_hwsampler_start ;
ops - > stop = oprofile_hwsampler_stop ;
return 0 ;
}
2011-03-16 12:10:12 +01:00
static void oprofile_hwsampler_exit ( void )
2011-02-15 13:02:14 -05:00
{
hwsampler_shutdown ( ) ;
}
2011-03-23 10:15:00 +01:00
# endif /* CONFIG_64BIT */
2011-03-16 12:10:12 +01:00
int __init oprofile_arch_init ( struct oprofile_operations * ops )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:19:16 -08:00
ops - > backtrace = s390_backtrace ;
2011-01-21 10:06:53 +00:00
2011-03-23 10:15:00 +01:00
# ifdef CONFIG_64BIT
2011-11-25 20:03:05 +01:00
/*
* - ENODEV is not reported to the caller . The module itself
* will use the timer mode sampling as fallback and this is
* always available .
*/
hwsampler_available = oprofile_hwsampler_init ( ops ) = = 0 ;
return 0 ;
2011-03-23 10:15:00 +01:00
# else
return - ENODEV ;
# endif
2005-04-16 15:20:36 -07:00
}
void oprofile_arch_exit ( void )
{
2011-03-23 10:15:00 +01:00
# ifdef CONFIG_64BIT
2011-01-21 10:06:53 +00:00
oprofile_hwsampler_exit ( ) ;
2011-03-23 10:15:00 +01:00
# endif
2005-04-16 15:20:36 -07:00
}