2005-04-16 15:20:36 -07:00
/*
* Copyright ( C ) 2002 , 2003 Broadcom Corporation
*
* 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 .
2005-09-03 15:56:17 -07:00
*
2005-04-16 15:20:36 -07:00
* 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 .
*/
2005-09-03 15:56:17 -07:00
/*
2005-04-16 15:20:36 -07:00
* The Bus Watcher monitors internal bus transactions and maintains
* counts of transactions with error status , logging details and
* causing one of several interrupts . This driver provides a handler
* for those interrupts which aggregates the counts ( to avoid
* saturating the 8 - bit counters ) and provides a presence in
* / proc / bus_watcher if PROC_FS is on .
*/
# include <linux/config.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/sched.h>
# include <linux/proc_fs.h>
# include <asm/system.h>
# include <asm/io.h>
# include <asm/sibyte/sb1250.h>
# include <asm/sibyte/sb1250_regs.h>
# include <asm/sibyte/sb1250_int.h>
# include <asm/sibyte/sb1250_scd.h>
struct bw_stats_struct {
uint64_t status ;
uint32_t l2_err ;
uint32_t memio_err ;
int status_printed ;
unsigned long l2_cor_d ;
unsigned long l2_bad_d ;
unsigned long l2_cor_t ;
unsigned long l2_bad_t ;
unsigned long mem_cor_d ;
unsigned long mem_bad_d ;
unsigned long bus_error ;
} bw_stats ;
static void print_summary ( uint32_t status , uint32_t l2_err ,
uint32_t memio_err )
{
printk ( " Bus watcher error counters: %08x %08x \n " , l2_err , memio_err ) ;
printk ( " \n Last recorded signature: \n " ) ;
printk ( " Request %02x from %d, answered by %d with Dcode %d \n " ,
( unsigned int ) ( G_SCD_BERR_TID ( status ) & 0x3f ) ,
( int ) ( G_SCD_BERR_TID ( status ) > > 6 ) ,
( int ) G_SCD_BERR_RID ( status ) ,
( int ) G_SCD_BERR_DCODE ( status ) ) ;
}
/*
* check_bus_watcher is exported for use in situations where we want
* to see the most recent status of the bus watcher , which might have
* already been destructively read out of the registers .
*
* notes : this is currently used by the cache error handler
* should provide locking against the interrupt handler
*/
void check_bus_watcher ( void )
{
u32 status , l2_err , memio_err ;
# ifdef CONFIG_SB1_PASS_1_WORKAROUNDS
/* Destructive read, clears register and interrupt */
status = csr_in32 ( IOADDR ( A_SCD_BUS_ERR_STATUS ) ) ;
# else
/* Use non-destructive register */
status = csr_in32 ( IOADDR ( A_SCD_BUS_ERR_STATUS_DEBUG ) ) ;
# endif
if ( ! ( status & 0x7fffffff ) ) {
printk ( " Using last values reaped by bus watcher driver \n " ) ;
status = bw_stats . status ;
l2_err = bw_stats . l2_err ;
memio_err = bw_stats . memio_err ;
} else {
l2_err = csr_in32 ( IOADDR ( A_BUS_L2_ERRORS ) ) ;
memio_err = csr_in32 ( IOADDR ( A_BUS_MEM_IO_ERRORS ) ) ;
}
if ( status & ~ ( 1UL < < 31 ) )
print_summary ( status , l2_err , memio_err ) ;
else
printk ( " Bus watcher indicates no error \n " ) ;
}
static int bw_print_buffer ( char * page , struct bw_stats_struct * stats )
{
int len ;
len = sprintf ( page , " SiByte Bus Watcher statistics \n " ) ;
len + = sprintf ( page + len , " ----------------------------- \n " ) ;
len + = sprintf ( page + len , " L2-d-cor %8ld \n L2-d-bad %8ld \n " ,
stats - > l2_cor_d , stats - > l2_bad_d ) ;
len + = sprintf ( page + len , " L2-t-cor %8ld \n L2-t-bad %8ld \n " ,
stats - > l2_cor_t , stats - > l2_bad_t ) ;
len + = sprintf ( page + len , " MC-d-cor %8ld \n MC-d-bad %8ld \n " ,
stats - > mem_cor_d , stats - > mem_bad_d ) ;
len + = sprintf ( page + len , " IO-err %8ld \n " , stats - > bus_error ) ;
len + = sprintf ( page + len , " \n Last recorded signature: \n " ) ;
len + = sprintf ( page + len , " Request %02x from %d, answered by %d with Dcode %d \n " ,
( unsigned int ) ( G_SCD_BERR_TID ( stats - > status ) & 0x3f ) ,
( int ) ( G_SCD_BERR_TID ( stats - > status ) > > 6 ) ,
( int ) G_SCD_BERR_RID ( stats - > status ) ,
( int ) G_SCD_BERR_DCODE ( stats - > status ) ) ;
/* XXXKW indicate multiple errors between printings, or stats
collection ( or both ) ? */
if ( stats - > status & M_SCD_BERR_MULTERRS )
len + = sprintf ( page + len , " Multiple errors observed since last check. \n " ) ;
if ( stats - > status_printed ) {
len + = sprintf ( page + len , " (no change since last printing) \n " ) ;
} else {
stats - > status_printed = 1 ;
}
return len ;
}
# ifdef CONFIG_PROC_FS
/* For simplicity, I want to assume a single read is required each
time */
static int bw_read_proc ( char * page , char * * start , off_t off ,
int count , int * eof , void * data )
{
int len ;
if ( off = = 0 ) {
len = bw_print_buffer ( page , data ) ;
* start = page ;
} else {
len = 0 ;
* eof = 1 ;
}
return len ;
}
static void create_proc_decoder ( struct bw_stats_struct * stats )
{
struct proc_dir_entry * ent ;
2005-09-03 15:56:17 -07:00
2005-04-16 15:20:36 -07:00
ent = create_proc_read_entry ( " bus_watcher " , S_IWUSR | S_IRUGO , NULL ,
bw_read_proc , stats ) ;
if ( ! ent ) {
printk ( KERN_INFO " Unable to initialize bus_watcher /proc entry \n " ) ;
return ;
}
}
# endif /* CONFIG_PROC_FS */
/*
* sibyte_bw_int - handle bus watcher interrupts and accumulate counts
*
* notes : possible re - entry due to multiple sources
* should check / indicate saturation
*/
static irqreturn_t sibyte_bw_int ( int irq , void * data , struct pt_regs * regs )
{
struct bw_stats_struct * stats = data ;
unsigned long cntr ;
# ifdef CONFIG_SIBYTE_BW_TRACE
int i ;
# endif
# ifndef CONFIG_PROC_FS
char bw_buf [ 1024 ] ;
# endif
# ifdef CONFIG_SIBYTE_BW_TRACE
csr_out32 ( M_SCD_TRACE_CFG_FREEZE , IOADDR ( A_SCD_TRACE_CFG ) ) ;
csr_out32 ( M_SCD_TRACE_CFG_START_READ , IOADDR ( A_SCD_TRACE_CFG ) ) ;
for ( i = 0 ; i < 256 * 6 ; i + + )
printk ( " %016llx \n " ,
2005-02-22 21:51:30 +00:00
( long long ) __raw_readq ( IOADDR ( A_SCD_TRACE_READ ) ) ) ;
2005-04-16 15:20:36 -07:00
csr_out32 ( M_SCD_TRACE_CFG_RESET , IOADDR ( A_SCD_TRACE_CFG ) ) ;
csr_out32 ( M_SCD_TRACE_CFG_START , IOADDR ( A_SCD_TRACE_CFG ) ) ;
# endif
/* Destructive read, clears register and interrupt */
stats - > status = csr_in32 ( IOADDR ( A_SCD_BUS_ERR_STATUS ) ) ;
stats - > status_printed = 0 ;
stats - > l2_err = cntr = csr_in32 ( IOADDR ( A_BUS_L2_ERRORS ) ) ;
stats - > l2_cor_d + = G_SCD_L2ECC_CORR_D ( cntr ) ;
stats - > l2_bad_d + = G_SCD_L2ECC_BAD_D ( cntr ) ;
stats - > l2_cor_t + = G_SCD_L2ECC_CORR_T ( cntr ) ;
stats - > l2_bad_t + = G_SCD_L2ECC_BAD_T ( cntr ) ;
csr_out32 ( 0 , IOADDR ( A_BUS_L2_ERRORS ) ) ;
stats - > memio_err = cntr = csr_in32 ( IOADDR ( A_BUS_MEM_IO_ERRORS ) ) ;
stats - > mem_cor_d + = G_SCD_MEM_ECC_CORR ( cntr ) ;
stats - > mem_bad_d + = G_SCD_MEM_ECC_BAD ( cntr ) ;
stats - > bus_error + = G_SCD_MEM_BUSERR ( cntr ) ;
csr_out32 ( 0 , IOADDR ( A_BUS_MEM_IO_ERRORS ) ) ;
# ifndef CONFIG_PROC_FS
bw_print_buffer ( bw_buf , stats ) ;
printk ( bw_buf ) ;
# endif
return IRQ_HANDLED ;
}
int __init sibyte_bus_watcher ( void )
{
memset ( & bw_stats , 0 , sizeof ( struct bw_stats_struct ) ) ;
bw_stats . status_printed = 1 ;
if ( request_irq ( K_INT_BAD_ECC , sibyte_bw_int , 0 , " Bus watcher " , & bw_stats ) ) {
printk ( " Failed to register bus watcher BAD_ECC irq \n " ) ;
return - 1 ;
}
if ( request_irq ( K_INT_COR_ECC , sibyte_bw_int , 0 , " Bus watcher " , & bw_stats ) ) {
free_irq ( K_INT_BAD_ECC , & bw_stats ) ;
printk ( " Failed to register bus watcher COR_ECC irq \n " ) ;
return - 1 ;
}
if ( request_irq ( K_INT_IO_BUS , sibyte_bw_int , 0 , " Bus watcher " , & bw_stats ) ) {
free_irq ( K_INT_BAD_ECC , & bw_stats ) ;
free_irq ( K_INT_COR_ECC , & bw_stats ) ;
printk ( " Failed to register bus watcher IO_BUS irq \n " ) ;
return - 1 ;
}
# ifdef CONFIG_PROC_FS
create_proc_decoder ( & bw_stats ) ;
# endif
# ifdef CONFIG_SIBYTE_BW_TRACE
csr_out32 ( ( M_SCD_TRSEQ_ASAMPLE | M_SCD_TRSEQ_DSAMPLE |
K_SCD_TRSEQ_TRIGGER_ALL ) ,
IOADDR ( A_SCD_TRACE_SEQUENCE_0 ) ) ;
csr_out32 ( M_SCD_TRACE_CFG_RESET , IOADDR ( A_SCD_TRACE_CFG ) ) ;
csr_out32 ( M_SCD_TRACE_CFG_START , IOADDR ( A_SCD_TRACE_CFG ) ) ;
# endif
return 0 ;
}
__initcall ( sibyte_bus_watcher ) ;