2005-02-07 05:54:29 +03:00
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
2006-10-07 22:44:33 +04:00
* Copyright ( C ) 2004 , 05 , 06 by Ralf Baechle
2005-02-07 05:54:29 +03:00
* Copyright ( C ) 2005 by MIPS Technologies , Inc .
*/
2007-12-06 12:12:28 +03:00
# include <linux/cpumask.h>
2005-02-07 05:54:29 +03:00
# include <linux/oprofile.h>
# include <linux/interrupt.h>
# include <linux/smp.h>
2006-10-07 22:44:33 +04:00
# include <asm/irq_regs.h>
2005-02-07 05:54:29 +03:00
# include "op_impl.h"
2006-06-23 21:39:00 +04:00
# define M_PERFCTL_EXL (1UL << 0)
# define M_PERFCTL_KERNEL (1UL << 1)
# define M_PERFCTL_SUPERVISOR (1UL << 2)
# define M_PERFCTL_USER (1UL << 3)
# define M_PERFCTL_INTERRUPT_ENABLE (1UL << 4)
2008-01-29 13:14:59 +03:00
# define M_PERFCTL_EVENT(event) (((event) & 0x3ff) << 5)
2006-06-23 21:39:00 +04:00
# define M_PERFCTL_VPEID(vpe) ((vpe) << 16)
# define M_PERFCTL_MT_EN(filter) ((filter) << 20)
# define M_TC_EN_ALL M_PERFCTL_MT_EN(0)
# define M_TC_EN_VPE M_PERFCTL_MT_EN(1)
# define M_TC_EN_TC M_PERFCTL_MT_EN(2)
# define M_PERFCTL_TCID(tcid) ((tcid) << 22)
# define M_PERFCTL_WIDE (1UL << 30)
# define M_PERFCTL_MORE (1UL << 31)
# define M_COUNTER_OVERFLOW (1UL << 31)
2008-04-02 03:58:38 +04:00
static int ( * save_perf_irq ) ( void ) ;
2006-06-23 21:39:00 +04:00
# ifdef CONFIG_MIPS_MT_SMP
2008-04-28 20:14:26 +04:00
static int cpu_has_mipsmt_pertccounters ;
# define WHAT (M_TC_EN_VPE | \
M_PERFCTL_VPEID ( cpu_data [ smp_processor_id ( ) ] . vpe_id ) )
# define vpe_id() (cpu_has_mipsmt_pertccounters ? \
0 : cpu_data [ smp_processor_id ( ) ] . vpe_id )
2007-12-06 12:12:28 +03:00
/*
* The number of bits to shift to convert between counters per core and
* counters per VPE . There is no reasonable interface atm to obtain the
* number of VPEs used by Linux and in the 34 K this number is fixed to two
* anyways so we hardcore a few things here for the moment . The way it ' s
* done here will ensure that oprofile VSMP kernel will run right on a lesser
* core like a 24 K also or with maxcpus = 1.
*/
static inline unsigned int vpe_shift ( void )
{
if ( num_possible_cpus ( ) > 1 )
return 1 ;
return 0 ;
}
2006-06-23 21:39:00 +04:00
# else
2007-12-06 12:12:28 +03:00
2006-10-23 16:22:06 +04:00
# define WHAT 0
2007-04-25 00:42:20 +04:00
# define vpe_id() 0
2007-12-06 12:12:28 +03:00
static inline unsigned int vpe_shift ( void )
{
return 0 ;
}
2006-06-23 21:39:00 +04:00
# endif
2005-02-07 05:54:29 +03:00
2007-12-06 12:12:28 +03:00
static inline unsigned int counters_total_to_per_cpu ( unsigned int counters )
{
return counters > > vpe_shift ( ) ;
}
static inline unsigned int counters_per_cpu_to_total ( unsigned int counters )
{
return counters < < vpe_shift ( ) ;
}
2006-06-23 21:39:00 +04:00
# define __define_perf_accessors(r, n, np) \
\
static inline unsigned int r_c0_ # # r # # n ( void ) \
{ \
2006-10-23 16:22:06 +04:00
unsigned int cpu = vpe_id ( ) ; \
2006-06-23 21:39:00 +04:00
\
switch ( cpu ) { \
case 0 : \
return read_c0_ # # r # # n ( ) ; \
case 1 : \
return read_c0_ # # r # # np ( ) ; \
default : \
BUG ( ) ; \
} \
2006-07-07 13:38:51 +04:00
return 0 ; \
2006-06-23 21:39:00 +04:00
} \
\
static inline void w_c0_ # # r # # n ( unsigned int value ) \
{ \
2006-10-23 16:22:06 +04:00
unsigned int cpu = vpe_id ( ) ; \
2006-06-23 21:39:00 +04:00
\
switch ( cpu ) { \
case 0 : \
write_c0_ # # r # # n ( value ) ; \
return ; \
case 1 : \
write_c0_ # # r # # np ( value ) ; \
return ; \
default : \
BUG ( ) ; \
} \
2006-07-07 13:38:51 +04:00
return ; \
2006-06-23 21:39:00 +04:00
} \
__define_perf_accessors ( perfcntr , 0 , 2 )
__define_perf_accessors ( perfcntr , 1 , 3 )
2007-03-01 20:58:24 +03:00
__define_perf_accessors ( perfcntr , 2 , 0 )
__define_perf_accessors ( perfcntr , 3 , 1 )
2006-06-23 21:39:00 +04:00
__define_perf_accessors ( perfctrl , 0 , 2 )
__define_perf_accessors ( perfctrl , 1 , 3 )
2007-03-01 20:58:24 +03:00
__define_perf_accessors ( perfctrl , 2 , 0 )
__define_perf_accessors ( perfctrl , 3 , 1 )
2005-02-07 05:54:29 +03:00
2006-05-23 11:42:38 +04:00
struct op_mips_model op_model_mipsxx_ops ;
2005-02-07 05:54:29 +03:00
static struct mipsxx_register_config {
unsigned int control [ 4 ] ;
unsigned int counter [ 4 ] ;
} reg ;
/* Compute all of the registers in preparation for enabling profiling. */
static void mipsxx_reg_setup ( struct op_counter_config * ctr )
{
2006-05-23 11:42:38 +04:00
unsigned int counters = op_model_mipsxx_ops . num_counters ;
2005-02-07 05:54:29 +03:00
int i ;
/* Compute the performance counter control word. */
for ( i = 0 ; i < counters ; i + + ) {
reg . control [ i ] = 0 ;
reg . counter [ i ] = 0 ;
if ( ! ctr [ i ] . enabled )
continue ;
reg . control [ i ] = M_PERFCTL_EVENT ( ctr [ i ] . event ) |
M_PERFCTL_INTERRUPT_ENABLE ;
if ( ctr [ i ] . kernel )
reg . control [ i ] | = M_PERFCTL_KERNEL ;
if ( ctr [ i ] . user )
reg . control [ i ] | = M_PERFCTL_USER ;
if ( ctr [ i ] . exl )
reg . control [ i ] | = M_PERFCTL_EXL ;
reg . counter [ i ] = 0x80000000 - ctr [ i ] . count ;
}
}
/* Program all of the registers in preparation for enabling profiling. */
2007-10-12 02:46:15 +04:00
static void mipsxx_cpu_setup ( void * args )
2005-02-07 05:54:29 +03:00
{
2006-05-23 11:42:38 +04:00
unsigned int counters = op_model_mipsxx_ops . num_counters ;
2005-02-07 05:54:29 +03:00
switch ( counters ) {
case 4 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl3 ( 0 ) ;
w_c0_perfcntr3 ( reg . counter [ 3 ] ) ;
2005-02-07 05:54:29 +03:00
case 3 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl2 ( 0 ) ;
w_c0_perfcntr2 ( reg . counter [ 2 ] ) ;
2005-02-07 05:54:29 +03:00
case 2 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl1 ( 0 ) ;
w_c0_perfcntr1 ( reg . counter [ 1 ] ) ;
2005-02-07 05:54:29 +03:00
case 1 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl0 ( 0 ) ;
w_c0_perfcntr0 ( reg . counter [ 0 ] ) ;
2005-02-07 05:54:29 +03:00
}
}
/* Start all counters on current CPU */
static void mipsxx_cpu_start ( void * args )
{
2006-05-23 11:42:38 +04:00
unsigned int counters = op_model_mipsxx_ops . num_counters ;
2005-02-07 05:54:29 +03:00
switch ( counters ) {
case 4 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl3 ( WHAT | reg . control [ 3 ] ) ;
2005-02-07 05:54:29 +03:00
case 3 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl2 ( WHAT | reg . control [ 2 ] ) ;
2005-02-07 05:54:29 +03:00
case 2 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl1 ( WHAT | reg . control [ 1 ] ) ;
2005-02-07 05:54:29 +03:00
case 1 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl0 ( WHAT | reg . control [ 0 ] ) ;
2005-02-07 05:54:29 +03:00
}
}
/* Stop all counters on current CPU */
static void mipsxx_cpu_stop ( void * args )
{
2006-05-23 11:42:38 +04:00
unsigned int counters = op_model_mipsxx_ops . num_counters ;
2005-02-07 05:54:29 +03:00
switch ( counters ) {
case 4 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl3 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 3 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl2 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 2 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl1 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 1 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl0 ( 0 ) ;
2005-02-07 05:54:29 +03:00
}
}
2006-10-07 22:44:33 +04:00
static int mipsxx_perfcount_handler ( void )
2005-02-07 05:54:29 +03:00
{
2006-05-23 11:42:38 +04:00
unsigned int counters = op_model_mipsxx_ops . num_counters ;
2005-02-07 05:54:29 +03:00
unsigned int control ;
unsigned int counter ;
2007-05-25 01:24:20 +04:00
int handled = IRQ_NONE ;
if ( cpu_has_mips_r2 & & ! ( read_c0_cause ( ) & ( 1 < < 26 ) ) )
return handled ;
2005-02-07 05:54:29 +03:00
switch ( counters ) {
# define HANDLE_COUNTER(n) \
case n + 1 : \
2006-06-23 21:39:00 +04:00
control = r_c0_perfctrl # # n ( ) ; \
counter = r_c0_perfcntr # # n ( ) ; \
2005-02-07 05:54:29 +03:00
if ( ( control & M_PERFCTL_INTERRUPT_ENABLE ) & & \
( counter & M_COUNTER_OVERFLOW ) ) { \
2006-10-07 22:44:33 +04:00
oprofile_add_sample ( get_irq_regs ( ) , n ) ; \
2006-06-23 21:39:00 +04:00
w_c0_perfcntr # # n ( reg . counter [ n ] ) ; \
2007-05-25 01:24:20 +04:00
handled = IRQ_HANDLED ; \
2005-02-07 05:54:29 +03:00
}
HANDLE_COUNTER ( 3 )
HANDLE_COUNTER ( 2 )
HANDLE_COUNTER ( 1 )
HANDLE_COUNTER ( 0 )
}
2005-12-09 15:29:38 +03:00
return handled ;
2005-02-07 05:54:29 +03:00
}
# define M_CONFIG1_PC (1 << 4)
2006-06-23 21:39:00 +04:00
static inline int __n_counters ( void )
2005-02-07 05:54:29 +03:00
{
if ( ! ( read_c0_config1 ( ) & M_CONFIG1_PC ) )
return 0 ;
2008-04-28 20:14:26 +04:00
if ( ! ( read_c0_perfctrl0 ( ) & M_PERFCTL_MORE ) )
2005-02-07 05:54:29 +03:00
return 1 ;
2008-04-28 20:14:26 +04:00
if ( ! ( read_c0_perfctrl1 ( ) & M_PERFCTL_MORE ) )
2005-02-07 05:54:29 +03:00
return 2 ;
2008-04-28 20:14:26 +04:00
if ( ! ( read_c0_perfctrl2 ( ) & M_PERFCTL_MORE ) )
2005-02-07 05:54:29 +03:00
return 3 ;
return 4 ;
}
2006-06-23 21:39:00 +04:00
static inline int n_counters ( void )
{
2006-10-23 03:44:02 +04:00
int counters ;
2007-10-12 02:46:15 +04:00
switch ( current_cpu_type ( ) ) {
2006-10-23 03:44:02 +04:00
case CPU_R10000 :
counters = 2 ;
2007-02-28 18:34:22 +03:00
break ;
2006-10-23 03:44:02 +04:00
case CPU_R12000 :
case CPU_R14000 :
counters = 4 ;
2007-02-28 18:34:22 +03:00
break ;
2006-10-23 03:44:02 +04:00
default :
counters = __n_counters ( ) ;
}
2006-06-23 21:39:00 +04:00
return counters ;
}
2008-04-28 20:14:26 +04:00
static void reset_counters ( void * arg )
2005-02-07 05:54:29 +03:00
{
2008-05-06 14:23:33 +04:00
int counters = ( int ) ( long ) arg ;
2005-02-07 05:54:29 +03:00
switch ( counters ) {
case 4 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl3 ( 0 ) ;
w_c0_perfcntr3 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 3 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl2 ( 0 ) ;
w_c0_perfcntr2 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 2 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl1 ( 0 ) ;
w_c0_perfcntr1 ( 0 ) ;
2005-02-07 05:54:29 +03:00
case 1 :
2006-06-23 21:39:00 +04:00
w_c0_perfctrl0 ( 0 ) ;
w_c0_perfcntr0 ( 0 ) ;
2005-02-07 05:54:29 +03:00
}
}
static int __init mipsxx_init ( void )
{
int counters ;
counters = n_counters ( ) ;
2005-12-09 15:34:45 +03:00
if ( counters = = 0 ) {
printk ( KERN_ERR " Oprofile: CPU has no performance counters \n " ) ;
2005-02-07 05:54:29 +03:00
return - ENODEV ;
2005-12-09 15:34:45 +03:00
}
2005-02-07 05:54:29 +03:00
2008-04-28 20:14:26 +04:00
# ifdef CONFIG_MIPS_MT_SMP
cpu_has_mipsmt_pertccounters = read_c0_config7 ( ) & ( 1 < < 19 ) ;
if ( ! cpu_has_mipsmt_pertccounters )
counters = counters_total_to_per_cpu ( counters ) ;
# endif
2008-07-16 00:08:52 +04:00
on_each_cpu ( reset_counters , ( void * ) ( long ) counters , 1 ) ;
2007-03-01 20:58:24 +03:00
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . num_counters = counters ;
2007-10-12 02:46:15 +04:00
switch ( current_cpu_type ( ) ) {
2005-12-09 15:42:13 +03:00
case CPU_20KC :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/20K " ;
2005-12-09 15:42:13 +03:00
break ;
2005-02-07 05:54:29 +03:00
case CPU_24K :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/24K " ;
2005-02-07 05:54:29 +03:00
break ;
2005-12-09 15:42:13 +03:00
case CPU_25KF :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/25K " ;
2005-12-09 15:42:13 +03:00
break ;
2008-04-28 20:14:26 +04:00
case CPU_1004K :
#if 0
/* FIXME: report as 34K for now */
op_model_mipsxx_ops . cpu_type = " mips/1004K " ;
break ;
# endif
2006-02-01 20:54:30 +03:00
case CPU_34K :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/34K " ;
2006-02-01 20:54:30 +03:00
break ;
2006-05-02 17:08:46 +04:00
case CPU_74K :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/74K " ;
2006-05-02 17:08:46 +04:00
break ;
2006-02-01 20:54:30 +03:00
2005-12-09 15:42:13 +03:00
case CPU_5KC :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/5K " ;
2005-12-09 15:42:13 +03:00
break ;
2006-10-23 03:44:02 +04:00
case CPU_R10000 :
if ( ( current_cpu_data . processor_id & 0xff ) = = 0x20 )
op_model_mipsxx_ops . cpu_type = " mips/r10000-v2.x " ;
else
op_model_mipsxx_ops . cpu_type = " mips/r10000 " ;
break ;
case CPU_R12000 :
case CPU_R14000 :
op_model_mipsxx_ops . cpu_type = " mips/r12000 " ;
break ;
2006-01-17 23:06:32 +03:00
case CPU_SB1 :
case CPU_SB1A :
2006-05-23 11:42:38 +04:00
op_model_mipsxx_ops . cpu_type = " mips/sb1 " ;
2006-01-17 23:06:32 +03:00
break ;
2005-02-07 05:54:29 +03:00
default :
printk ( KERN_ERR " Profiling unsupported for this CPU \n " ) ;
return - ENODEV ;
}
2008-04-02 03:58:38 +04:00
save_perf_irq = perf_irq ;
2005-02-07 05:54:29 +03:00
perf_irq = mipsxx_perfcount_handler ;
return 0 ;
}
static void mipsxx_exit ( void )
{
2007-03-01 20:58:24 +03:00
int counters = op_model_mipsxx_ops . num_counters ;
2007-12-06 12:12:28 +03:00
counters = counters_per_cpu_to_total ( counters ) ;
2008-07-16 00:08:52 +04:00
on_each_cpu ( reset_counters , ( void * ) ( long ) counters , 1 ) ;
2005-02-07 05:54:29 +03:00
2008-04-02 03:58:38 +04:00
perf_irq = save_perf_irq ;
2005-02-07 05:54:29 +03:00
}
2006-05-23 11:42:38 +04:00
struct op_mips_model op_model_mipsxx_ops = {
2005-02-07 05:54:29 +03:00
. reg_setup = mipsxx_reg_setup ,
. cpu_setup = mipsxx_cpu_setup ,
. init = mipsxx_init ,
. exit = mipsxx_exit ,
. cpu_start = mipsxx_cpu_start ,
. cpu_stop = mipsxx_cpu_stop ,
} ;