2006-09-07 03:23:12 +04:00
/*
* Copyright ( C ) 2006 Mike Kravetz IBM Corporation
*
* Hypervisor Call Instrumentation
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <linux/percpu.h>
# include <linux/debugfs.h>
# include <linux/seq_file.h>
# include <linux/cpumask.h>
# include <asm/hvcall.h>
# include <asm/firmware.h>
# include <asm/cputable.h>
2009-10-26 21:50:29 +03:00
# include <asm/trace.h>
2006-09-07 03:23:12 +04:00
DEFINE_PER_CPU ( struct hcall_stats [ HCALL_STAT_ARRAY_SIZE ] , hcall_stats ) ;
/*
* Routines for displaying the statistics in debugfs
*/
static void * hc_start ( struct seq_file * m , loff_t * pos )
{
2007-01-08 18:43:02 +03:00
if ( ( int ) * pos < ( HCALL_STAT_ARRAY_SIZE - 1 ) )
2006-09-07 03:23:12 +04:00
return ( void * ) ( unsigned long ) ( * pos + 1 ) ;
return NULL ;
}
static void * hc_next ( struct seq_file * m , void * p , loff_t * pos )
{
+ + * pos ;
return hc_start ( m , pos ) ;
}
static void hc_stop ( struct seq_file * m , void * p )
{
}
static int hc_show ( struct seq_file * m , void * p )
{
unsigned long h_num = ( unsigned long ) p ;
2010-09-28 06:04:44 +04:00
struct hcall_stats * hs = m - > private ;
2006-09-07 03:23:12 +04:00
if ( hs [ h_num ] . num_calls ) {
2007-01-08 18:43:02 +03:00
if ( cpu_has_feature ( CPU_FTR_PURR ) )
2006-09-07 03:23:12 +04:00
seq_printf ( m , " %lu %lu %lu %lu \n " , h_num < < 2 ,
hs [ h_num ] . num_calls ,
hs [ h_num ] . tb_total ,
hs [ h_num ] . purr_total ) ;
else
seq_printf ( m , " %lu %lu %lu \n " , h_num < < 2 ,
hs [ h_num ] . num_calls ,
hs [ h_num ] . tb_total ) ;
}
return 0 ;
}
2009-09-23 03:43:43 +04:00
static const struct seq_operations hcall_inst_seq_ops = {
2006-09-07 03:23:12 +04:00
. start = hc_start ,
. next = hc_next ,
. stop = hc_stop ,
. show = hc_show
} ;
static int hcall_inst_seq_open ( struct inode * inode , struct file * file )
{
int rc ;
struct seq_file * seq ;
rc = seq_open ( file , & hcall_inst_seq_ops ) ;
seq = file - > private_data ;
2006-12-08 13:37:30 +03:00
seq - > private = file - > f_path . dentry - > d_inode - > i_private ;
2006-09-07 03:23:12 +04:00
return rc ;
}
2007-02-12 11:55:31 +03:00
static const struct file_operations hcall_inst_seq_fops = {
2006-09-07 03:23:12 +04:00
. open = hcall_inst_seq_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
# define HCALL_ROOT_DIR "hcall_inst"
# define CPU_NAME_BUF_SIZE 32
2009-10-26 21:50:29 +03:00
2010-05-28 09:05:00 +04:00
static void probe_hcall_entry ( void * ignored , unsigned long opcode , unsigned long * args )
2009-10-26 21:50:29 +03:00
{
struct hcall_stats * h ;
if ( opcode > MAX_HCALL_OPCODE )
return ;
h = & get_cpu_var ( hcall_stats ) [ opcode / 4 ] ;
h - > tb_start = mftb ( ) ;
h - > purr_start = mfspr ( SPRN_PURR ) ;
}
2010-05-28 09:05:00 +04:00
static void probe_hcall_exit ( void * ignored , unsigned long opcode , unsigned long retval ,
2009-10-26 21:51:09 +03:00
unsigned long * retbuf )
2009-10-26 21:50:29 +03:00
{
struct hcall_stats * h ;
if ( opcode > MAX_HCALL_OPCODE )
return ;
h = & __get_cpu_var ( hcall_stats ) [ opcode / 4 ] ;
h - > num_calls + + ;
2009-11-25 09:12:09 +03:00
h - > tb_total + = mftb ( ) - h - > tb_start ;
h - > purr_total + = mfspr ( SPRN_PURR ) - h - > purr_start ;
2009-10-26 21:50:29 +03:00
put_cpu_var ( hcall_stats ) ;
}
2006-09-07 03:23:12 +04:00
static int __init hcall_inst_init ( void )
{
struct dentry * hcall_root ;
struct dentry * hcall_file ;
char cpu_name_buf [ CPU_NAME_BUF_SIZE ] ;
int cpu ;
if ( ! firmware_has_feature ( FW_FEATURE_LPAR ) )
return 0 ;
2010-05-28 09:05:00 +04:00
if ( register_trace_hcall_entry ( probe_hcall_entry , NULL ) )
2009-10-26 21:50:29 +03:00
return - EINVAL ;
2010-05-28 09:05:00 +04:00
if ( register_trace_hcall_exit ( probe_hcall_exit , NULL ) ) {
unregister_trace_hcall_entry ( probe_hcall_entry , NULL ) ;
2009-10-26 21:50:29 +03:00
return - EINVAL ;
}
2006-09-07 03:23:12 +04:00
hcall_root = debugfs_create_dir ( HCALL_ROOT_DIR , NULL ) ;
if ( ! hcall_root )
return - ENOMEM ;
for_each_possible_cpu ( cpu ) {
snprintf ( cpu_name_buf , CPU_NAME_BUF_SIZE , " cpu%d " , cpu ) ;
hcall_file = debugfs_create_file ( cpu_name_buf , S_IRUGO ,
hcall_root ,
per_cpu ( hcall_stats , cpu ) ,
& hcall_inst_seq_fops ) ;
if ( ! hcall_file )
return - ENOMEM ;
}
return 0 ;
}
__initcall ( hcall_inst_init ) ;