2008-01-18 23:32:46 +03:00
/*
* PS3 Logical Performance Monitor .
*
* Copyright ( C ) 2007 Sony Computer Entertainment Inc .
* Copyright 2007 Sony Corp .
*
* 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 ; version 2 of the License .
*
* 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/module.h>
# include <linux/interrupt.h>
# include <linux/uaccess.h>
2008-11-02 10:26:51 +03:00
# include <asm/smp.h>
2008-05-01 02:25:09 +04:00
# include <asm/time.h>
2008-01-18 23:32:46 +03:00
# include <asm/ps3.h>
# include <asm/lv1call.h>
# include <asm/cell-pmu.h>
/* BOOKMARK tag macros */
# define PS3_PM_BOOKMARK_START 0x8000000000000000ULL
# define PS3_PM_BOOKMARK_STOP 0x4000000000000000ULL
# define PS3_PM_BOOKMARK_TAG_KERNEL 0x1000000000000000ULL
# define PS3_PM_BOOKMARK_TAG_USER 0x3000000000000000ULL
# define PS3_PM_BOOKMARK_TAG_MASK_HI 0xF000000000000000ULL
# define PS3_PM_BOOKMARK_TAG_MASK_LO 0x0F00000000000000ULL
/* CBE PM CONTROL register macros */
# define PS3_PM_CONTROL_PPU_TH0_BOOKMARK 0x00001000
# define PS3_PM_CONTROL_PPU_TH1_BOOKMARK 0x00000800
# define PS3_PM_CONTROL_PPU_COUNT_MODE_MASK 0x000C0000
# define PS3_PM_CONTROL_PPU_COUNT_MODE_PROBLEM 0x00080000
# define PS3_WRITE_PM_MASK 0xFFFFFFFFFFFFFFFFULL
/* CBE PM START STOP register macros */
# define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START 0x02000000
# define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START 0x01000000
# define PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP 0x00020000
# define PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP 0x00010000
# define PS3_PM_START_STOP_START_MASK 0xFF000000
# define PS3_PM_START_STOP_STOP_MASK 0x00FF0000
/* CBE PM COUNTER register macres */
# define PS3_PM_COUNTER_MASK_HI 0xFFFFFFFF00000000ULL
# define PS3_PM_COUNTER_MASK_LO 0x00000000FFFFFFFFULL
/* BASE SIGNAL GROUP NUMBER macros */
# define PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER 0
# define PM_ISLAND2_SIGNAL_GROUP_NUMBER1 6
# define PM_ISLAND2_SIGNAL_GROUP_NUMBER2 7
# define PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER 7
# define PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER 15
# define PM_SPU_TRIGGER_SIGNAL_GROUP_NUMBER 17
# define PM_SPU_EVENT_SIGNAL_GROUP_NUMBER 18
# define PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER 18
# define PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER 24
# define PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER 49
# define PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER 52
# define PM_SIG_GROUP_SPU 41
# define PM_SIG_GROUP_SPU_TRIGGER 42
# define PM_SIG_GROUP_SPU_EVENT 43
# define PM_SIG_GROUP_MFC_MAX 60
/**
* struct ps3_lpm_shadow_regs - Performance monitor shadow registers .
*
* @ pm_control : Shadow of the processor ' s pm_control register .
* @ pm_start_stop : Shadow of the processor ' s pm_start_stop register .
* @ group_control : Shadow of the processor ' s group_control register .
* @ debug_bus_control : Shadow of the processor ' s debug_bus_control register .
*
* The logical performance monitor provides a write - only interface to
* these processor registers . These shadow variables cache the processor
* register values for reading .
*
* The initial value of the shadow registers at lpm creation is
* PS3_LPM_SHADOW_REG_INIT .
*/
struct ps3_lpm_shadow_regs {
u64 pm_control ;
u64 pm_start_stop ;
u64 group_control ;
u64 debug_bus_control ;
} ;
# define PS3_LPM_SHADOW_REG_INIT 0xFFFFFFFF00000000ULL
/**
* struct ps3_lpm_priv - Private lpm device data .
*
* @ open : An atomic variable indicating the lpm driver has been opened .
* @ rights : The lpm rigths granted by the system policy module . A logical
* OR of enum ps3_lpm_rights .
* @ node_id : The node id of a BE prosessor whose performance monitor this
* lpar has the right to use .
* @ pu_id : The lv1 id of the logical PU .
* @ lpm_id : The lv1 id of this lpm instance .
* @ outlet_id : The outlet created by lv1 for this lpm instance .
* @ tb_count : The number of bytes of data held in the lv1 trace buffer .
* @ tb_cache : Kernel buffer to receive the data from the lv1 trace buffer .
* Must be 128 byte aligned .
* @ tb_cache_size : Size of the kernel @ tb_cache buffer . Must be 128 byte
* aligned .
* @ tb_cache_internal : An unaligned buffer allocated by this driver to be
* used for the trace buffer cache when ps3_lpm_open ( ) is called with a
* NULL tb_cache argument . Otherwise unused .
* @ shadow : Processor register shadow of type struct ps3_lpm_shadow_regs .
* @ sbd : The struct ps3_system_bus_device attached to this driver .
*
* The trace buffer is a buffer allocated and used internally to the lv1
* hypervisor to collect trace data . The trace buffer cache is a guest
* buffer that accepts the trace data from the trace buffer .
*/
struct ps3_lpm_priv {
atomic_t open ;
u64 rights ;
u64 node_id ;
u64 pu_id ;
u64 lpm_id ;
u64 outlet_id ;
u64 tb_count ;
void * tb_cache ;
u64 tb_cache_size ;
void * tb_cache_internal ;
struct ps3_lpm_shadow_regs shadow ;
struct ps3_system_bus_device * sbd ;
} ;
enum {
PS3_LPM_DEFAULT_TB_CACHE_SIZE = 0x4000 ,
} ;
/**
* lpm_priv - Static instance of the lpm data .
*
* Since the exported routines don ' t support the notion of a device
* instance we need to hold the instance in this static variable
* and then only allow at most one instance at a time to be created .
*/
static struct ps3_lpm_priv * lpm_priv ;
static struct device * sbd_core ( void )
{
BUG_ON ( ! lpm_priv | | ! lpm_priv - > sbd ) ;
return & lpm_priv - > sbd - > core ;
}
/**
* use_start_stop_bookmark - Enable the PPU bookmark trace .
*
* And it enables PPU bookmark triggers ONLY if the other triggers are not set .
* The start / stop bookmarks are inserted at ps3_enable_pm ( ) and ps3_disable_pm ( )
* to start / stop LPM .
*
* Used to get good quality of the performance counter .
*/
enum { use_start_stop_bookmark = 1 , } ;
void ps3_set_bookmark ( u64 bookmark )
{
/*
* As per the PPE book IV , to avoid bookmark loss there must
* not be a traced branch within 10 cycles of setting the
* SPRN_BKMK register . The actual text is unclear if ' within '
* includes cycles before the call .
*/
2008-02-09 01:52:30 +03:00
asm volatile ( " nop;nop;nop;nop;nop;nop;nop;nop;nop; " ) ;
2008-01-18 23:32:46 +03:00
mtspr ( SPRN_BKMK , bookmark ) ;
2008-02-09 01:52:30 +03:00
asm volatile ( " nop;nop;nop;nop;nop;nop;nop;nop;nop; " ) ;
2008-01-18 23:32:46 +03:00
}
EXPORT_SYMBOL_GPL ( ps3_set_bookmark ) ;
void ps3_set_pm_bookmark ( u64 tag , u64 incident , u64 th_id )
{
u64 bookmark ;
bookmark = ( get_tb ( ) & 0x00000000FFFFFFFFULL ) |
PS3_PM_BOOKMARK_TAG_KERNEL ;
bookmark = ( ( tag < < 56 ) & PS3_PM_BOOKMARK_TAG_MASK_LO ) |
( incident < < 48 ) | ( th_id < < 32 ) | bookmark ;
ps3_set_bookmark ( bookmark ) ;
}
EXPORT_SYMBOL_GPL ( ps3_set_pm_bookmark ) ;
/**
* ps3_read_phys_ctr - Read physical counter registers .
*
* Each physical counter can act as one 32 bit counter or as two 16 bit
* counters .
*/
u32 ps3_read_phys_ctr ( u32 cpu , u32 phys_ctr )
{
int result ;
u64 counter0415 ;
u64 counter2637 ;
if ( phys_ctr > = NR_PHYS_CTRS ) {
dev_dbg ( sbd_core ( ) , " %s:%u: phys_ctr too big: %u \n " , __func__ ,
__LINE__ , phys_ctr ) ;
return 0 ;
}
result = lv1_set_lpm_counter ( lpm_priv - > lpm_id , 0 , 0 , 0 , 0 , & counter0415 ,
& counter2637 ) ;
if ( result ) {
dev_err ( sbd_core ( ) , " %s:%u: lv1_set_lpm_counter failed: "
" phys_ctr %u, %s \n " , __func__ , __LINE__ , phys_ctr ,
ps3_result ( result ) ) ;
return 0 ;
}
switch ( phys_ctr ) {
case 0 :
return counter0415 > > 32 ;
case 1 :
return counter0415 & PS3_PM_COUNTER_MASK_LO ;
case 2 :
return counter2637 > > 32 ;
case 3 :
return counter2637 & PS3_PM_COUNTER_MASK_LO ;
default :
BUG ( ) ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_read_phys_ctr ) ;
/**
* ps3_write_phys_ctr - Write physical counter registers .
*
* Each physical counter can act as one 32 bit counter or as two 16 bit
* counters .
*/
void ps3_write_phys_ctr ( u32 cpu , u32 phys_ctr , u32 val )
{
u64 counter0415 ;
u64 counter0415_mask ;
u64 counter2637 ;
u64 counter2637_mask ;
int result ;
if ( phys_ctr > = NR_PHYS_CTRS ) {
dev_dbg ( sbd_core ( ) , " %s:%u: phys_ctr too big: %u \n " , __func__ ,
__LINE__ , phys_ctr ) ;
return ;
}
switch ( phys_ctr ) {
case 0 :
counter0415 = ( u64 ) val < < 32 ;
counter0415_mask = PS3_PM_COUNTER_MASK_HI ;
counter2637 = 0x0 ;
counter2637_mask = 0x0 ;
break ;
case 1 :
counter0415 = ( u64 ) val ;
counter0415_mask = PS3_PM_COUNTER_MASK_LO ;
counter2637 = 0x0 ;
counter2637_mask = 0x0 ;
break ;
case 2 :
counter0415 = 0x0 ;
counter0415_mask = 0x0 ;
counter2637 = ( u64 ) val < < 32 ;
counter2637_mask = PS3_PM_COUNTER_MASK_HI ;
break ;
case 3 :
counter0415 = 0x0 ;
counter0415_mask = 0x0 ;
counter2637 = ( u64 ) val ;
counter2637_mask = PS3_PM_COUNTER_MASK_LO ;
break ;
default :
BUG ( ) ;
}
result = lv1_set_lpm_counter ( lpm_priv - > lpm_id ,
counter0415 , counter0415_mask ,
counter2637 , counter2637_mask ,
& counter0415 , & counter2637 ) ;
if ( result )
dev_err ( sbd_core ( ) , " %s:%u: lv1_set_lpm_counter failed: "
" phys_ctr %u, val %u, %s \n " , __func__ , __LINE__ ,
phys_ctr , val , ps3_result ( result ) ) ;
}
EXPORT_SYMBOL_GPL ( ps3_write_phys_ctr ) ;
/**
* ps3_read_ctr - Read counter .
*
* Read 16 or 32 bits depending on the current size of the counter .
* Counters 4 , 5 , 6 & 7 are always 16 bit .
*/
u32 ps3_read_ctr ( u32 cpu , u32 ctr )
{
u32 val ;
u32 phys_ctr = ctr & ( NR_PHYS_CTRS - 1 ) ;
val = ps3_read_phys_ctr ( cpu , phys_ctr ) ;
if ( ps3_get_ctr_size ( cpu , phys_ctr ) = = 16 )
val = ( ctr < NR_PHYS_CTRS ) ? ( val > > 16 ) : ( val & 0xffff ) ;
return val ;
}
EXPORT_SYMBOL_GPL ( ps3_read_ctr ) ;
/**
* ps3_write_ctr - Write counter .
*
* Write 16 or 32 bits depending on the current size of the counter .
* Counters 4 , 5 , 6 & 7 are always 16 bit .
*/
void ps3_write_ctr ( u32 cpu , u32 ctr , u32 val )
{
u32 phys_ctr ;
u32 phys_val ;
phys_ctr = ctr & ( NR_PHYS_CTRS - 1 ) ;
if ( ps3_get_ctr_size ( cpu , phys_ctr ) = = 16 ) {
phys_val = ps3_read_phys_ctr ( cpu , phys_ctr ) ;
if ( ctr < NR_PHYS_CTRS )
val = ( val < < 16 ) | ( phys_val & 0xffff ) ;
else
val = ( val & 0xffff ) | ( phys_val & 0xffff0000 ) ;
}
ps3_write_phys_ctr ( cpu , phys_ctr , val ) ;
}
EXPORT_SYMBOL_GPL ( ps3_write_ctr ) ;
/**
* ps3_read_pm07_control - Read counter control registers .
*
* Each logical counter has a corresponding control register .
*/
u32 ps3_read_pm07_control ( u32 cpu , u32 ctr )
{
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_read_pm07_control ) ;
/**
* ps3_write_pm07_control - Write counter control registers .
*
* Each logical counter has a corresponding control register .
*/
void ps3_write_pm07_control ( u32 cpu , u32 ctr , u32 val )
{
int result ;
static const u64 mask = 0xFFFFFFFFFFFFFFFFULL ;
u64 old_value ;
if ( ctr > = NR_CTRS ) {
dev_dbg ( sbd_core ( ) , " %s:%u: ctr too big: %u \n " , __func__ ,
__LINE__ , ctr ) ;
return ;
}
result = lv1_set_lpm_counter_control ( lpm_priv - > lpm_id , ctr , val , mask ,
& old_value ) ;
if ( result )
dev_err ( sbd_core ( ) , " %s:%u: lv1_set_lpm_counter_control "
" failed: ctr %u, %s \n " , __func__ , __LINE__ , ctr ,
ps3_result ( result ) ) ;
}
EXPORT_SYMBOL_GPL ( ps3_write_pm07_control ) ;
/**
* ps3_read_pm - Read Other LPM control registers .
*/
u32 ps3_read_pm ( u32 cpu , enum pm_reg_name reg )
{
int result = 0 ;
u64 val = 0 ;
switch ( reg ) {
case pm_control :
return lpm_priv - > shadow . pm_control ;
case trace_address :
return CBE_PM_TRACE_BUF_EMPTY ;
case pm_start_stop :
return lpm_priv - > shadow . pm_start_stop ;
case pm_interval :
2008-02-09 01:52:41 +03:00
result = lv1_set_lpm_interval ( lpm_priv - > lpm_id , 0 , 0 , & val ) ;
if ( result ) {
val = 0 ;
dev_dbg ( sbd_core ( ) , " %s:%u: lv1 set_inteval failed: "
" reg %u, %s \n " , __func__ , __LINE__ , reg ,
ps3_result ( result ) ) ;
}
return ( u32 ) val ;
2008-01-18 23:32:46 +03:00
case group_control :
return lpm_priv - > shadow . group_control ;
case debug_bus_control :
return lpm_priv - > shadow . debug_bus_control ;
case pm_status :
result = lv1_get_lpm_interrupt_status ( lpm_priv - > lpm_id ,
& val ) ;
if ( result ) {
val = 0 ;
dev_dbg ( sbd_core ( ) , " %s:%u: lv1 get_lpm_status failed: "
" reg %u, %s \n " , __func__ , __LINE__ , reg ,
ps3_result ( result ) ) ;
}
return ( u32 ) val ;
case ext_tr_timer :
return 0 ;
default :
dev_dbg ( sbd_core ( ) , " %s:%u: unknown reg: %d \n " , __func__ ,
__LINE__ , reg ) ;
BUG ( ) ;
break ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_read_pm ) ;
/**
* ps3_write_pm - Write Other LPM control registers .
*/
void ps3_write_pm ( u32 cpu , enum pm_reg_name reg , u32 val )
{
int result = 0 ;
u64 dummy ;
switch ( reg ) {
case group_control :
if ( val ! = lpm_priv - > shadow . group_control )
result = lv1_set_lpm_group_control ( lpm_priv - > lpm_id ,
val ,
PS3_WRITE_PM_MASK ,
& dummy ) ;
lpm_priv - > shadow . group_control = val ;
break ;
case debug_bus_control :
if ( val ! = lpm_priv - > shadow . debug_bus_control )
result = lv1_set_lpm_debug_bus_control ( lpm_priv - > lpm_id ,
val ,
PS3_WRITE_PM_MASK ,
& dummy ) ;
lpm_priv - > shadow . debug_bus_control = val ;
break ;
case pm_control :
if ( use_start_stop_bookmark )
val | = ( PS3_PM_CONTROL_PPU_TH0_BOOKMARK |
PS3_PM_CONTROL_PPU_TH1_BOOKMARK ) ;
if ( val ! = lpm_priv - > shadow . pm_control )
result = lv1_set_lpm_general_control ( lpm_priv - > lpm_id ,
val ,
PS3_WRITE_PM_MASK ,
0 , 0 , & dummy ,
& dummy ) ;
lpm_priv - > shadow . pm_control = val ;
break ;
case pm_interval :
2008-02-09 01:52:41 +03:00
result = lv1_set_lpm_interval ( lpm_priv - > lpm_id , val ,
PS3_WRITE_PM_MASK , & dummy ) ;
2008-01-18 23:32:46 +03:00
break ;
case pm_start_stop :
if ( val ! = lpm_priv - > shadow . pm_start_stop )
result = lv1_set_lpm_trigger_control ( lpm_priv - > lpm_id ,
val ,
PS3_WRITE_PM_MASK ,
& dummy ) ;
lpm_priv - > shadow . pm_start_stop = val ;
break ;
case trace_address :
case ext_tr_timer :
case pm_status :
break ;
default :
dev_dbg ( sbd_core ( ) , " %s:%u: unknown reg: %d \n " , __func__ ,
__LINE__ , reg ) ;
BUG ( ) ;
break ;
}
if ( result )
dev_err ( sbd_core ( ) , " %s:%u: lv1 set_control failed: "
" reg %u, %s \n " , __func__ , __LINE__ , reg ,
ps3_result ( result ) ) ;
}
EXPORT_SYMBOL_GPL ( ps3_write_pm ) ;
/**
* ps3_get_ctr_size - Get the size of a physical counter .
*
* Returns either 16 or 32.
*/
u32 ps3_get_ctr_size ( u32 cpu , u32 phys_ctr )
{
u32 pm_ctrl ;
if ( phys_ctr > = NR_PHYS_CTRS ) {
dev_dbg ( sbd_core ( ) , " %s:%u: phys_ctr too big: %u \n " , __func__ ,
__LINE__ , phys_ctr ) ;
return 0 ;
}
pm_ctrl = ps3_read_pm ( cpu , pm_control ) ;
return ( pm_ctrl & CBE_PM_16BIT_CTR ( phys_ctr ) ) ? 16 : 32 ;
}
EXPORT_SYMBOL_GPL ( ps3_get_ctr_size ) ;
/**
* ps3_set_ctr_size - Set the size of a physical counter to 16 or 32 bits .
*/
void ps3_set_ctr_size ( u32 cpu , u32 phys_ctr , u32 ctr_size )
{
u32 pm_ctrl ;
if ( phys_ctr > = NR_PHYS_CTRS ) {
dev_dbg ( sbd_core ( ) , " %s:%u: phys_ctr too big: %u \n " , __func__ ,
__LINE__ , phys_ctr ) ;
return ;
}
pm_ctrl = ps3_read_pm ( cpu , pm_control ) ;
switch ( ctr_size ) {
case 16 :
pm_ctrl | = CBE_PM_16BIT_CTR ( phys_ctr ) ;
ps3_write_pm ( cpu , pm_control , pm_ctrl ) ;
break ;
case 32 :
pm_ctrl & = ~ CBE_PM_16BIT_CTR ( phys_ctr ) ;
ps3_write_pm ( cpu , pm_control , pm_ctrl ) ;
break ;
default :
BUG ( ) ;
}
}
EXPORT_SYMBOL_GPL ( ps3_set_ctr_size ) ;
static u64 pm_translate_signal_group_number_on_island2 ( u64 subgroup )
{
if ( subgroup = = 2 )
subgroup = 3 ;
if ( subgroup < = 6 )
return PM_ISLAND2_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
else if ( subgroup = = 7 )
return PM_ISLAND2_SIGNAL_GROUP_NUMBER1 ;
else
return PM_ISLAND2_SIGNAL_GROUP_NUMBER2 ;
}
static u64 pm_translate_signal_group_number_on_island3 ( u64 subgroup )
{
switch ( subgroup ) {
case 2 :
case 3 :
case 4 :
subgroup + = 2 ;
break ;
case 5 :
subgroup = 8 ;
break ;
default :
break ;
}
return PM_ISLAND3_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
}
static u64 pm_translate_signal_group_number_on_island4 ( u64 subgroup )
{
return PM_ISLAND4_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
}
static u64 pm_translate_signal_group_number_on_island5 ( u64 subgroup )
{
switch ( subgroup ) {
case 3 :
subgroup = 4 ;
break ;
case 4 :
subgroup = 6 ;
break ;
default :
break ;
}
return PM_ISLAND5_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
}
static u64 pm_translate_signal_group_number_on_island6 ( u64 subgroup ,
u64 subsubgroup )
{
switch ( subgroup ) {
case 3 :
case 4 :
case 5 :
subgroup + = 1 ;
break ;
default :
break ;
}
switch ( subsubgroup ) {
case 4 :
case 5 :
case 6 :
subsubgroup + = 2 ;
break ;
case 7 :
case 8 :
case 9 :
case 10 :
subsubgroup + = 4 ;
break ;
case 11 :
case 12 :
case 13 :
subsubgroup + = 5 ;
break ;
default :
break ;
}
if ( subgroup < = 5 )
return ( PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup ) ;
else
return ( PM_ISLAND6_BASE_SIGNAL_GROUP_NUMBER + subgroup
+ subsubgroup - 1 ) ;
}
static u64 pm_translate_signal_group_number_on_island7 ( u64 subgroup )
{
return PM_ISLAND7_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
}
static u64 pm_translate_signal_group_number_on_island8 ( u64 subgroup )
{
return PM_ISLAND8_BASE_SIGNAL_GROUP_NUMBER + subgroup ;
}
static u64 pm_signal_group_to_ps3_lv1_signal_group ( u64 group )
{
u64 island ;
u64 subgroup ;
u64 subsubgroup ;
subgroup = 0 ;
subsubgroup = 0 ;
island = 0 ;
if ( group < 1000 ) {
if ( group < 100 ) {
if ( 20 < = group & & group < 30 ) {
island = 2 ;
subgroup = group - 20 ;
} else if ( 30 < = group & & group < 40 ) {
island = 3 ;
subgroup = group - 30 ;
} else if ( 40 < = group & & group < 50 ) {
island = 4 ;
subgroup = group - 40 ;
} else if ( 50 < = group & & group < 60 ) {
island = 5 ;
subgroup = group - 50 ;
} else if ( 60 < = group & & group < 70 ) {
island = 6 ;
subgroup = group - 60 ;
} else if ( 70 < = group & & group < 80 ) {
island = 7 ;
subgroup = group - 70 ;
} else if ( 80 < = group & & group < 90 ) {
island = 8 ;
subgroup = group - 80 ;
}
} else if ( 200 < = group & & group < 300 ) {
island = 2 ;
subgroup = group - 200 ;
} else if ( 600 < = group & & group < 700 ) {
island = 6 ;
subgroup = 5 ;
subsubgroup = group - 650 ;
}
} else if ( 6000 < = group & & group < 7000 ) {
island = 6 ;
subgroup = 5 ;
subsubgroup = group - 6500 ;
}
switch ( island ) {
case 2 :
return pm_translate_signal_group_number_on_island2 ( subgroup ) ;
case 3 :
return pm_translate_signal_group_number_on_island3 ( subgroup ) ;
case 4 :
return pm_translate_signal_group_number_on_island4 ( subgroup ) ;
case 5 :
return pm_translate_signal_group_number_on_island5 ( subgroup ) ;
case 6 :
return pm_translate_signal_group_number_on_island6 ( subgroup ,
subsubgroup ) ;
case 7 :
return pm_translate_signal_group_number_on_island7 ( subgroup ) ;
case 8 :
return pm_translate_signal_group_number_on_island8 ( subgroup ) ;
default :
2009-01-13 23:10:06 +03:00
dev_dbg ( sbd_core ( ) , " %s:%u: island not found: %llu \n " , __func__ ,
2008-01-18 23:32:46 +03:00
__LINE__ , group ) ;
BUG ( ) ;
break ;
}
return 0 ;
}
static u64 pm_bus_word_to_ps3_lv1_bus_word ( u8 word )
{
switch ( word ) {
case 1 :
return 0xF000 ;
case 2 :
return 0x0F00 ;
case 4 :
return 0x00F0 ;
case 8 :
default :
return 0x000F ;
}
}
static int __ps3_set_signal ( u64 lv1_signal_group , u64 bus_select ,
u64 signal_select , u64 attr1 , u64 attr2 , u64 attr3 )
{
int ret ;
ret = lv1_set_lpm_signal ( lpm_priv - > lpm_id , lv1_signal_group , bus_select ,
signal_select , attr1 , attr2 , attr3 ) ;
if ( ret )
dev_err ( sbd_core ( ) ,
2009-01-13 23:10:06 +03:00
" %s:%u: error:%d 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx \n " ,
2008-01-18 23:32:46 +03:00
__func__ , __LINE__ , ret , lv1_signal_group , bus_select ,
signal_select , attr1 , attr2 , attr3 ) ;
return ret ;
}
int ps3_set_signal ( u64 signal_group , u8 signal_bit , u16 sub_unit ,
u8 bus_word )
{
int ret ;
u64 lv1_signal_group ;
u64 bus_select ;
u64 signal_select ;
u64 attr1 , attr2 , attr3 ;
if ( signal_group = = 0 )
return __ps3_set_signal ( 0 , 0 , 0 , 0 , 0 , 0 ) ;
lv1_signal_group =
pm_signal_group_to_ps3_lv1_signal_group ( signal_group ) ;
bus_select = pm_bus_word_to_ps3_lv1_bus_word ( bus_word ) ;
switch ( signal_group ) {
case PM_SIG_GROUP_SPU_TRIGGER :
signal_select = 1 ;
signal_select = signal_select < < ( 63 - signal_bit ) ;
break ;
case PM_SIG_GROUP_SPU_EVENT :
signal_select = 1 ;
signal_select = ( signal_select < < ( 63 - signal_bit ) ) | 0x3 ;
break ;
default :
signal_select = 0 ;
break ;
}
/*
* 0 : physical object .
* 1 : logical object .
* This parameter is only used for the PPE and SPE signals .
*/
attr1 = 1 ;
/*
* This parameter is used to specify the target physical / logical
* PPE / SPE object .
*/
if ( PM_SIG_GROUP_SPU < = signal_group & &
signal_group < PM_SIG_GROUP_MFC_MAX )
attr2 = sub_unit ;
else
attr2 = lpm_priv - > pu_id ;
/*
* This parameter is only used for setting the SPE signal .
*/
attr3 = 0 ;
ret = __ps3_set_signal ( lv1_signal_group , bus_select , signal_select ,
attr1 , attr2 , attr3 ) ;
if ( ret )
dev_err ( sbd_core ( ) , " %s:%u: __ps3_set_signal failed: %d \n " ,
__func__ , __LINE__ , ret ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( ps3_set_signal ) ;
u32 ps3_get_hw_thread_id ( int cpu )
{
return get_hard_smp_processor_id ( cpu ) ;
}
EXPORT_SYMBOL_GPL ( ps3_get_hw_thread_id ) ;
/**
* ps3_enable_pm - Enable the entire performance monitoring unit .
*
* When we enable the LPM , all pending writes to counters get committed .
*/
void ps3_enable_pm ( u32 cpu )
{
int result ;
u64 tmp ;
int insert_bookmark = 0 ;
lpm_priv - > tb_count = 0 ;
if ( use_start_stop_bookmark ) {
if ( ! ( lpm_priv - > shadow . pm_start_stop &
( PS3_PM_START_STOP_START_MASK
| PS3_PM_START_STOP_STOP_MASK ) ) ) {
result = lv1_set_lpm_trigger_control ( lpm_priv - > lpm_id ,
( PS3_PM_START_STOP_PPU_TH0_BOOKMARK_START |
PS3_PM_START_STOP_PPU_TH1_BOOKMARK_START |
PS3_PM_START_STOP_PPU_TH0_BOOKMARK_STOP |
PS3_PM_START_STOP_PPU_TH1_BOOKMARK_STOP ) ,
0xFFFFFFFFFFFFFFFFULL , & tmp ) ;
if ( result )
dev_err ( sbd_core ( ) , " %s:%u: "
" lv1_set_lpm_trigger_control failed: "
" %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
insert_bookmark = ! result ;
}
}
result = lv1_start_lpm ( lpm_priv - > lpm_id ) ;
if ( result )
dev_err ( sbd_core ( ) , " %s:%u: lv1_start_lpm failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
if ( use_start_stop_bookmark & & ! result & & insert_bookmark )
ps3_set_bookmark ( get_tb ( ) | PS3_PM_BOOKMARK_START ) ;
}
EXPORT_SYMBOL_GPL ( ps3_enable_pm ) ;
/**
* ps3_disable_pm - Disable the entire performance monitoring unit .
*/
void ps3_disable_pm ( u32 cpu )
{
int result ;
u64 tmp ;
ps3_set_bookmark ( get_tb ( ) | PS3_PM_BOOKMARK_STOP ) ;
result = lv1_stop_lpm ( lpm_priv - > lpm_id , & tmp ) ;
if ( result ) {
if ( result ! = LV1_WRONG_STATE )
dev_err ( sbd_core ( ) , " %s:%u: lv1_stop_lpm failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
return ;
}
lpm_priv - > tb_count = tmp ;
2009-01-13 23:10:06 +03:00
dev_dbg ( sbd_core ( ) , " %s:%u: tb_count %llu (%llxh) \n " , __func__ , __LINE__ ,
2008-01-18 23:32:46 +03:00
lpm_priv - > tb_count , lpm_priv - > tb_count ) ;
}
EXPORT_SYMBOL_GPL ( ps3_disable_pm ) ;
/**
* ps3_lpm_copy_tb - Copy data from the trace buffer to a kernel buffer .
* @ offset : Offset in bytes from the start of the trace buffer .
* @ buf : Copy destination .
* @ count : Maximum count of bytes to copy .
* @ bytes_copied : Pointer to a variable that will recieve the number of
* bytes copied to @ buf .
*
* On error @ buf will contain any successfully copied trace buffer data
* and bytes_copied will be set to the number of bytes successfully copied .
*/
int ps3_lpm_copy_tb ( unsigned long offset , void * buf , unsigned long count ,
unsigned long * bytes_copied )
{
int result ;
* bytes_copied = 0 ;
if ( ! lpm_priv - > tb_cache )
return - EPERM ;
if ( offset > = lpm_priv - > tb_count )
return 0 ;
2009-01-13 23:10:06 +03:00
count = min_t ( u64 , count , lpm_priv - > tb_count - offset ) ;
2008-01-18 23:32:46 +03:00
while ( * bytes_copied < count ) {
const unsigned long request = count - * bytes_copied ;
u64 tmp ;
result = lv1_copy_lpm_trace_buffer ( lpm_priv - > lpm_id , offset ,
request , & tmp ) ;
if ( result ) {
dev_dbg ( sbd_core ( ) , " %s:%u: 0x%lx bytes at 0x%lx \n " ,
__func__ , __LINE__ , request , offset ) ;
dev_err ( sbd_core ( ) , " %s:%u: lv1_copy_lpm_trace_buffer "
" failed: %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
return result = = LV1_WRONG_STATE ? - EBUSY : - EINVAL ;
}
memcpy ( buf , lpm_priv - > tb_cache , tmp ) ;
buf + = tmp ;
* bytes_copied + = tmp ;
offset + = tmp ;
}
dev_dbg ( sbd_core ( ) , " %s:%u: copied %lxh bytes \n " , __func__ , __LINE__ ,
* bytes_copied ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_lpm_copy_tb ) ;
/**
* ps3_lpm_copy_tb_to_user - Copy data from the trace buffer to a user buffer .
* @ offset : Offset in bytes from the start of the trace buffer .
* @ buf : A __user copy destination .
* @ count : Maximum count of bytes to copy .
* @ bytes_copied : Pointer to a variable that will recieve the number of
* bytes copied to @ buf .
*
* On error @ buf will contain any successfully copied trace buffer data
* and bytes_copied will be set to the number of bytes successfully copied .
*/
int ps3_lpm_copy_tb_to_user ( unsigned long offset , void __user * buf ,
unsigned long count , unsigned long * bytes_copied )
{
int result ;
* bytes_copied = 0 ;
if ( ! lpm_priv - > tb_cache )
return - EPERM ;
if ( offset > = lpm_priv - > tb_count )
return 0 ;
2009-01-13 23:10:06 +03:00
count = min_t ( u64 , count , lpm_priv - > tb_count - offset ) ;
2008-01-18 23:32:46 +03:00
while ( * bytes_copied < count ) {
const unsigned long request = count - * bytes_copied ;
u64 tmp ;
result = lv1_copy_lpm_trace_buffer ( lpm_priv - > lpm_id , offset ,
request , & tmp ) ;
if ( result ) {
dev_dbg ( sbd_core ( ) , " %s:%u: 0x%lx bytes at 0x%lx \n " ,
__func__ , __LINE__ , request , offset ) ;
dev_err ( sbd_core ( ) , " %s:%u: lv1_copy_lpm_trace_buffer "
" failed: %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
return result = = LV1_WRONG_STATE ? - EBUSY : - EINVAL ;
}
result = copy_to_user ( buf , lpm_priv - > tb_cache , tmp ) ;
if ( result ) {
2009-01-13 23:10:06 +03:00
dev_dbg ( sbd_core ( ) , " %s:%u: 0x%llx bytes at 0x%p \n " ,
2008-01-18 23:32:46 +03:00
__func__ , __LINE__ , tmp , buf ) ;
dev_err ( sbd_core ( ) , " %s:%u: copy_to_user failed: %d \n " ,
__func__ , __LINE__ , result ) ;
return - EFAULT ;
}
buf + = tmp ;
* bytes_copied + = tmp ;
offset + = tmp ;
}
dev_dbg ( sbd_core ( ) , " %s:%u: copied %lxh bytes \n " , __func__ , __LINE__ ,
* bytes_copied ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_lpm_copy_tb_to_user ) ;
/**
* ps3_get_and_clear_pm_interrupts -
*
* Clearing interrupts for the entire performance monitoring unit .
* Reading pm_status clears the interrupt bits .
*/
u32 ps3_get_and_clear_pm_interrupts ( u32 cpu )
{
return ps3_read_pm ( cpu , pm_status ) ;
}
EXPORT_SYMBOL_GPL ( ps3_get_and_clear_pm_interrupts ) ;
/**
* ps3_enable_pm_interrupts -
*
* Enabling interrupts for the entire performance monitoring unit .
* Enables the interrupt bits in the pm_status register .
*/
void ps3_enable_pm_interrupts ( u32 cpu , u32 thread , u32 mask )
{
if ( mask )
ps3_write_pm ( cpu , pm_status , mask ) ;
}
EXPORT_SYMBOL_GPL ( ps3_enable_pm_interrupts ) ;
/**
* ps3_enable_pm_interrupts -
*
* Disabling interrupts for the entire performance monitoring unit .
*/
void ps3_disable_pm_interrupts ( u32 cpu )
{
ps3_get_and_clear_pm_interrupts ( cpu ) ;
ps3_write_pm ( cpu , pm_status , 0 ) ;
}
EXPORT_SYMBOL_GPL ( ps3_disable_pm_interrupts ) ;
/**
* ps3_lpm_open - Open the logical performance monitor device .
* @ tb_type : Specifies the type of trace buffer lv1 sould use for this lpm
* instance , specified by one of enum ps3_lpm_tb_type .
* @ tb_cache : Optional user supplied buffer to use as the trace buffer cache .
* If NULL , the driver will allocate and manage an internal buffer .
* Unused when when @ tb_type is PS3_LPM_TB_TYPE_NONE .
* @ tb_cache_size : The size in bytes of the user supplied @ tb_cache buffer .
* Unused when @ tb_cache is NULL or @ tb_type is PS3_LPM_TB_TYPE_NONE .
*/
int ps3_lpm_open ( enum ps3_lpm_tb_type tb_type , void * tb_cache ,
u64 tb_cache_size )
{
int result ;
u64 tb_size ;
BUG_ON ( ! lpm_priv ) ;
BUG_ON ( tb_type ! = PS3_LPM_TB_TYPE_NONE
& & tb_type ! = PS3_LPM_TB_TYPE_INTERNAL ) ;
if ( tb_type = = PS3_LPM_TB_TYPE_NONE & & tb_cache )
dev_dbg ( sbd_core ( ) , " %s:%u: bad in vals \n " , __func__ , __LINE__ ) ;
if ( ! atomic_add_unless ( & lpm_priv - > open , 1 , 1 ) ) {
dev_dbg ( sbd_core ( ) , " %s:%u: busy \n " , __func__ , __LINE__ ) ;
return - EBUSY ;
}
/* Note tb_cache needs 128 byte alignment. */
if ( tb_type = = PS3_LPM_TB_TYPE_NONE ) {
lpm_priv - > tb_cache_size = 0 ;
lpm_priv - > tb_cache_internal = NULL ;
lpm_priv - > tb_cache = NULL ;
} else if ( tb_cache ) {
if ( tb_cache ! = ( void * ) _ALIGN_UP ( ( unsigned long ) tb_cache , 128 )
| | tb_cache_size ! = _ALIGN_UP ( tb_cache_size , 128 ) ) {
dev_err ( sbd_core ( ) , " %s:%u: unaligned tb_cache \n " ,
__func__ , __LINE__ ) ;
result = - EINVAL ;
goto fail_align ;
}
lpm_priv - > tb_cache_size = tb_cache_size ;
lpm_priv - > tb_cache_internal = NULL ;
lpm_priv - > tb_cache = tb_cache ;
} else {
lpm_priv - > tb_cache_size = PS3_LPM_DEFAULT_TB_CACHE_SIZE ;
lpm_priv - > tb_cache_internal = kzalloc (
lpm_priv - > tb_cache_size + 127 , GFP_KERNEL ) ;
if ( ! lpm_priv - > tb_cache_internal ) {
dev_err ( sbd_core ( ) , " %s:%u: alloc internal tb_cache "
" failed \n " , __func__ , __LINE__ ) ;
result = - ENOMEM ;
goto fail_malloc ;
}
lpm_priv - > tb_cache = ( void * ) _ALIGN_UP (
( unsigned long ) lpm_priv - > tb_cache_internal , 128 ) ;
}
result = lv1_construct_lpm ( lpm_priv - > node_id , tb_type , 0 , 0 ,
ps3_mm_phys_to_lpar ( __pa ( lpm_priv - > tb_cache ) ) ,
lpm_priv - > tb_cache_size , & lpm_priv - > lpm_id ,
& lpm_priv - > outlet_id , & tb_size ) ;
if ( result ) {
dev_err ( sbd_core ( ) , " %s:%u: lv1_construct_lpm failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
result = - EINVAL ;
goto fail_construct ;
}
lpm_priv - > shadow . pm_control = PS3_LPM_SHADOW_REG_INIT ;
lpm_priv - > shadow . pm_start_stop = PS3_LPM_SHADOW_REG_INIT ;
lpm_priv - > shadow . group_control = PS3_LPM_SHADOW_REG_INIT ;
lpm_priv - > shadow . debug_bus_control = PS3_LPM_SHADOW_REG_INIT ;
2009-01-13 23:10:06 +03:00
dev_dbg ( sbd_core ( ) , " %s:%u: lpm_id 0x%llx, outlet_id 0x%llx, "
" tb_size 0x%llx \n " , __func__ , __LINE__ , lpm_priv - > lpm_id ,
2008-01-18 23:32:46 +03:00
lpm_priv - > outlet_id , tb_size ) ;
return 0 ;
fail_construct :
kfree ( lpm_priv - > tb_cache_internal ) ;
lpm_priv - > tb_cache_internal = NULL ;
fail_malloc :
fail_align :
atomic_dec ( & lpm_priv - > open ) ;
return result ;
}
EXPORT_SYMBOL_GPL ( ps3_lpm_open ) ;
/**
* ps3_lpm_close - Close the lpm device .
*
*/
int ps3_lpm_close ( void )
{
dev_dbg ( sbd_core ( ) , " %s:%u \n " , __func__ , __LINE__ ) ;
lv1_destruct_lpm ( lpm_priv - > lpm_id ) ;
lpm_priv - > lpm_id = 0 ;
kfree ( lpm_priv - > tb_cache_internal ) ;
lpm_priv - > tb_cache_internal = NULL ;
atomic_dec ( & lpm_priv - > open ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ps3_lpm_close ) ;
static int __devinit ps3_lpm_probe ( struct ps3_system_bus_device * dev )
{
dev_dbg ( & dev - > core , " -> %s:%u \n " , __func__ , __LINE__ ) ;
if ( lpm_priv ) {
dev_info ( & dev - > core , " %s:%u: called twice \n " ,
__func__ , __LINE__ ) ;
return - EBUSY ;
}
lpm_priv = kzalloc ( sizeof ( * lpm_priv ) , GFP_KERNEL ) ;
if ( ! lpm_priv )
return - ENOMEM ;
lpm_priv - > sbd = dev ;
lpm_priv - > node_id = dev - > lpm . node_id ;
lpm_priv - > pu_id = dev - > lpm . pu_id ;
lpm_priv - > rights = dev - > lpm . rights ;
dev_info ( & dev - > core , " <- %s:%u: \n " , __func__ , __LINE__ ) ;
return 0 ;
}
static int ps3_lpm_remove ( struct ps3_system_bus_device * dev )
{
dev_dbg ( & dev - > core , " -> %s:%u: \n " , __func__ , __LINE__ ) ;
ps3_lpm_close ( ) ;
kfree ( lpm_priv ) ;
lpm_priv = NULL ;
dev_info ( & dev - > core , " <- %s:%u: \n " , __func__ , __LINE__ ) ;
return 0 ;
}
static struct ps3_system_bus_driver ps3_lpm_driver = {
. match_id = PS3_MATCH_ID_LPM ,
. core . name = " ps3-lpm " ,
. core . owner = THIS_MODULE ,
. probe = ps3_lpm_probe ,
. remove = ps3_lpm_remove ,
. shutdown = ps3_lpm_remove ,
} ;
static int __init ps3_lpm_init ( void )
{
pr_debug ( " %s:%d: \n " , __func__ , __LINE__ ) ;
return ps3_system_bus_driver_register ( & ps3_lpm_driver ) ;
}
static void __exit ps3_lpm_exit ( void )
{
pr_debug ( " %s:%d: \n " , __func__ , __LINE__ ) ;
ps3_system_bus_driver_unregister ( & ps3_lpm_driver ) ;
}
module_init ( ps3_lpm_init ) ;
module_exit ( ps3_lpm_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " PS3 Logical Performance Monitor Driver " ) ;
MODULE_AUTHOR ( " Sony Corporation " ) ;
MODULE_ALIAS ( PS3_MODULE_ALIAS_LPM ) ;