2006-01-19 04:44:13 +03:00
/*
* edac_mc kernel module
2006-07-10 15:45:19 +04:00
* ( C ) 2005 , 2006 Linux Networx ( http : //lnxi.com)
2006-01-19 04:44:13 +03:00
* This file may be distributed under the terms of the
* GNU General Public License .
*
* Written by Thayne Harbaugh
* Based on work by Dan Hollis < goemon at anime dot net > and others .
* http : //www.anime.net/~goemon/linux-ecc/
*
* Modified by Dave Peterson and Doug Thompson
*
*/
# include <linux/module.h>
# include <linux/proc_fs.h>
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/smp.h>
# include <linux/init.h>
# include <linux/sysctl.h>
# include <linux/highmem.h>
# include <linux/timer.h>
# include <linux/slab.h>
# include <linux/jiffies.h>
# include <linux/spinlock.h>
# include <linux/list.h>
# include <linux/sysdev.h>
# include <linux/ctype.h>
# include <asm/uaccess.h>
# include <asm/page.h>
# include <asm/edac.h>
# include "edac_mc.h"
2007-07-19 12:49:33 +04:00
# include "edac_module.h"
2006-01-19 04:44:13 +03:00
/* lock to memory controller's control array */
static DECLARE_MUTEX ( mem_ctls_mutex ) ;
static struct list_head mc_devices = LIST_HEAD_INIT ( mc_devices ) ;
# ifdef CONFIG_EDAC_DEBUG
2007-07-19 12:49:32 +04:00
static void edac_mc_dump_channel ( struct channel_info * chan )
2006-01-19 04:44:13 +03:00
{
debugf4 ( " \t channel = %p \n " , chan ) ;
debugf4 ( " \t channel->chan_idx = %d \n " , chan - > chan_idx ) ;
debugf4 ( " \t channel->ce_count = %d \n " , chan - > ce_count ) ;
debugf4 ( " \t channel->label = '%s' \n " , chan - > label ) ;
debugf4 ( " \t channel->csrow = %p \n \n " , chan - > csrow ) ;
}
2007-07-19 12:49:32 +04:00
static void edac_mc_dump_csrow ( struct csrow_info * csrow )
2006-01-19 04:44:13 +03:00
{
debugf4 ( " \t csrow = %p \n " , csrow ) ;
debugf4 ( " \t csrow->csrow_idx = %d \n " , csrow - > csrow_idx ) ;
debugf4 ( " \t csrow->first_page = 0x%lx \n " ,
csrow - > first_page ) ;
debugf4 ( " \t csrow->last_page = 0x%lx \n " , csrow - > last_page ) ;
debugf4 ( " \t csrow->page_mask = 0x%lx \n " , csrow - > page_mask ) ;
debugf4 ( " \t csrow->nr_pages = 0x%x \n " , csrow - > nr_pages ) ;
debugf4 ( " \t csrow->nr_channels = %d \n " ,
csrow - > nr_channels ) ;
debugf4 ( " \t csrow->channels = %p \n " , csrow - > channels ) ;
debugf4 ( " \t csrow->mci = %p \n \n " , csrow - > mci ) ;
}
2007-07-19 12:49:32 +04:00
static void edac_mc_dump_mci ( struct mem_ctl_info * mci )
2006-01-19 04:44:13 +03:00
{
debugf3 ( " \t mci = %p \n " , mci ) ;
debugf3 ( " \t mci->mtype_cap = %lx \n " , mci - > mtype_cap ) ;
debugf3 ( " \t mci->edac_ctl_cap = %lx \n " , mci - > edac_ctl_cap ) ;
debugf3 ( " \t mci->edac_cap = %lx \n " , mci - > edac_cap ) ;
debugf4 ( " \t mci->edac_check = %p \n " , mci - > edac_check ) ;
debugf3 ( " \t mci->nr_csrows = %d, csrows = %p \n " ,
mci - > nr_csrows , mci - > csrows ) ;
2006-06-30 12:56:07 +04:00
debugf3 ( " \t dev = %p \n " , mci - > dev ) ;
2006-01-19 04:44:13 +03:00
debugf3 ( " \t mod_name:ctl_name = %s:%s \n " ,
mci - > mod_name , mci - > ctl_name ) ;
debugf3 ( " \t pvt_info = %p \n \n " , mci - > pvt_info ) ;
}
2006-03-26 13:38:52 +04:00
# endif /* CONFIG_EDAC_DEBUG */
2006-01-19 04:44:13 +03:00
/* 'ptr' points to a possibly unaligned item X such that sizeof(X) is 'size'.
* Adjust ' ptr ' so that its alignment is at least as stringent as what the
* compiler would provide for X and return the aligned result .
*
* If ' size ' is a constant , the compiler will optimize this whole function
* down to either a no - op or the addition of a constant to the value of ' ptr ' .
*/
2007-07-19 12:49:36 +04:00
char * edac_align_ptr ( void * ptr , unsigned size )
2006-01-19 04:44:13 +03:00
{
unsigned align , r ;
/* Here we assume that the alignment of a "long long" is the most
* stringent alignment that the compiler will ever provide by default .
* As far as I know , this is a reasonable assumption .
*/
if ( size > sizeof ( long ) )
align = sizeof ( long long ) ;
else if ( size > sizeof ( int ) )
align = sizeof ( long ) ;
else if ( size > sizeof ( short ) )
align = sizeof ( int ) ;
else if ( size > sizeof ( char ) )
align = sizeof ( short ) ;
else
return ( char * ) ptr ;
r = size % align ;
if ( r = = 0 )
return ( char * ) ptr ;
return ( char * ) ( ( ( unsigned long ) ptr ) + align - r ) ;
}
/**
* edac_mc_alloc : Allocate a struct mem_ctl_info structure
* @ size_pvt : size of private storage needed
* @ nr_csrows : Number of CWROWS needed for this MC
* @ nr_chans : Number of channels for the MC
*
* Everything is kmalloc ' ed as one big chunk - more efficient .
* Only can be used if all structures have the same lifetime - otherwise
* you have to allocate and initialize your own structures .
*
* Use edac_mc_free ( ) to free mc structures allocated by this function .
*
* Returns :
* NULL allocation failed
* struct mem_ctl_info pointer
*/
struct mem_ctl_info * edac_mc_alloc ( unsigned sz_pvt , unsigned nr_csrows ,
2006-03-26 13:38:52 +04:00
unsigned nr_chans )
2006-01-19 04:44:13 +03:00
{
struct mem_ctl_info * mci ;
struct csrow_info * csi , * csrow ;
struct channel_info * chi , * chp , * chan ;
void * pvt ;
unsigned size ;
int row , chn ;
/* Figure out the offsets of the various items from the start of an mc
* structure . We want the alignment of each item to be at least as
* stringent as what the compiler would provide if we could simply
* hardcode everything into a single struct .
*/
mci = ( struct mem_ctl_info * ) 0 ;
2007-07-19 12:49:36 +04:00
csi = ( struct csrow_info * ) edac_align_ptr ( & mci [ 1 ] , sizeof ( * csi ) ) ;
2006-01-19 04:44:13 +03:00
chi = ( struct channel_info * )
2007-07-19 12:49:36 +04:00
edac_align_ptr ( & csi [ nr_csrows ] , sizeof ( * chi ) ) ;
pvt = edac_align_ptr ( & chi [ nr_chans * nr_csrows ] , sz_pvt ) ;
2006-01-19 04:44:13 +03:00
size = ( ( unsigned long ) pvt ) + sz_pvt ;
if ( ( mci = kmalloc ( size , GFP_KERNEL ) ) = = NULL )
return NULL ;
/* Adjust pointers so they point within the memory we just allocated
* rather than an imaginary chunk of memory located at address 0.
*/
csi = ( struct csrow_info * ) ( ( ( char * ) mci ) + ( ( unsigned long ) csi ) ) ;
chi = ( struct channel_info * ) ( ( ( char * ) mci ) + ( ( unsigned long ) chi ) ) ;
pvt = sz_pvt ? ( ( ( char * ) mci ) + ( ( unsigned long ) pvt ) ) : NULL ;
2006-03-26 13:38:52 +04:00
memset ( mci , 0 , size ) ; /* clear all fields */
2006-01-19 04:44:13 +03:00
mci - > csrows = csi ;
mci - > pvt_info = pvt ;
mci - > nr_csrows = nr_csrows ;
for ( row = 0 ; row < nr_csrows ; row + + ) {
csrow = & csi [ row ] ;
csrow - > csrow_idx = row ;
csrow - > mci = mci ;
csrow - > nr_channels = nr_chans ;
chp = & chi [ row * nr_chans ] ;
csrow - > channels = chp ;
for ( chn = 0 ; chn < nr_chans ; chn + + ) {
chan = & chp [ chn ] ;
chan - > chan_idx = chn ;
chan - > csrow = csrow ;
}
}
return mci ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_alloc ) ;
2006-01-19 04:44:13 +03:00
/**
* edac_mc_free : Free a previously allocated ' mci ' structure
* @ mci : pointer to a struct mem_ctl_info structure
*/
void edac_mc_free ( struct mem_ctl_info * mci )
{
2006-03-26 13:38:49 +04:00
kfree ( mci ) ;
2006-01-19 04:44:13 +03:00
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_free ) ;
2006-01-19 04:44:13 +03:00
2006-06-30 12:56:07 +04:00
static struct mem_ctl_info * find_mci_by_dev ( struct device * dev )
2006-01-19 04:44:13 +03:00
{
struct mem_ctl_info * mci ;
struct list_head * item ;
2006-03-26 13:38:40 +04:00
debugf3 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:13 +03:00
list_for_each ( item , & mc_devices ) {
mci = list_entry ( item , struct mem_ctl_info , link ) ;
2006-06-30 12:56:07 +04:00
if ( mci - > dev = = dev )
2006-01-19 04:44:13 +03:00
return mci ;
}
return NULL ;
}
2006-06-30 12:56:08 +04:00
/* Return 0 on success, 1 on failure.
* Before calling this function , caller must
* assign a unique value to mci - > mc_idx .
*/
static int add_mc_to_global_list ( struct mem_ctl_info * mci )
2006-01-19 04:44:13 +03:00
{
struct list_head * item , * insert_before ;
struct mem_ctl_info * p ;
2006-06-30 12:56:08 +04:00
insert_before = & mc_devices ;
2006-01-19 04:44:13 +03:00
2006-06-30 12:56:08 +04:00
if ( unlikely ( ( p = find_mci_by_dev ( mci - > dev ) ) ! = NULL ) )
goto fail0 ;
2006-01-19 04:44:13 +03:00
2006-06-30 12:56:08 +04:00
list_for_each ( item , & mc_devices ) {
p = list_entry ( item , struct mem_ctl_info , link ) ;
2006-01-19 04:44:13 +03:00
2006-06-30 12:56:08 +04:00
if ( p - > mc_idx > = mci - > mc_idx ) {
if ( unlikely ( p - > mc_idx = = mci - > mc_idx ) )
goto fail1 ;
2006-01-19 04:44:13 +03:00
2006-06-30 12:56:08 +04:00
insert_before = item ;
break ;
2006-01-19 04:44:13 +03:00
}
}
list_add_tail_rcu ( & mci - > link , insert_before ) ;
return 0 ;
2006-06-30 12:56:08 +04:00
fail0 :
edac_printk ( KERN_WARNING , EDAC_MC ,
" %s (%s) %s %s already assigned %d \n " , p - > dev - > bus_id ,
dev_name ( p - > dev ) , p - > mod_name , p - > ctl_name , p - > mc_idx ) ;
return 1 ;
fail1 :
edac_printk ( KERN_WARNING , EDAC_MC ,
" bug in low-level driver: attempt to assign \n "
" duplicate mc_idx %d in %s() \n " , p - > mc_idx , __func__ ) ;
return 1 ;
2006-01-19 04:44:13 +03:00
}
2006-03-26 13:38:52 +04:00
static void complete_mc_list_del ( struct rcu_head * head )
2006-03-26 13:38:46 +04:00
{
struct mem_ctl_info * mci ;
mci = container_of ( head , struct mem_ctl_info , rcu ) ;
INIT_LIST_HEAD ( & mci - > link ) ;
complete ( & mci - > complete ) ;
}
2006-03-26 13:38:52 +04:00
static void del_mc_from_global_list ( struct mem_ctl_info * mci )
2006-03-26 13:38:46 +04:00
{
list_del_rcu ( & mci - > link ) ;
init_completion ( & mci - > complete ) ;
call_rcu ( & mci - > rcu , complete_mc_list_del ) ;
wait_for_completion ( & mci - > complete ) ;
}
2007-07-19 12:49:31 +04:00
/**
* edac_mc_find : Search for a mem_ctl_info structure whose index is ' idx ' .
*
* If found , return a pointer to the structure .
* Else return NULL .
*
* Caller must hold mem_ctls_mutex .
*/
struct mem_ctl_info * edac_mc_find ( int idx )
{
struct list_head * item ;
struct mem_ctl_info * mci ;
list_for_each ( item , & mc_devices ) {
mci = list_entry ( item , struct mem_ctl_info , link ) ;
if ( mci - > mc_idx > = idx ) {
if ( mci - > mc_idx = = idx )
return mci ;
break ;
}
}
return NULL ;
}
EXPORT_SYMBOL ( edac_mc_find ) ;
2006-01-19 04:44:13 +03:00
/**
2006-03-26 13:38:49 +04:00
* edac_mc_add_mc : Insert the ' mci ' structure into the mci global list and
* create sysfs entries associated with mci structure
2006-01-19 04:44:13 +03:00
* @ mci : pointer to the mci structure to be added to the list
2006-06-30 12:56:08 +04:00
* @ mc_idx : A unique numeric identifier to be assigned to the ' mci ' structure .
2006-01-19 04:44:13 +03:00
*
* Return :
* 0 Success
* ! 0 Failure
*/
/* FIXME - should a warning be printed if no error detection? correction? */
2006-06-30 12:56:08 +04:00
int edac_mc_add_mc ( struct mem_ctl_info * mci , int mc_idx )
2006-01-19 04:44:13 +03:00
{
2006-03-26 13:38:40 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2006-06-30 12:56:08 +04:00
mci - > mc_idx = mc_idx ;
2006-01-19 04:44:13 +03:00
# ifdef CONFIG_EDAC_DEBUG
if ( edac_debug_level > = 3 )
edac_mc_dump_mci ( mci ) ;
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:13 +03:00
if ( edac_debug_level > = 4 ) {
int i ;
for ( i = 0 ; i < mci - > nr_csrows ; i + + ) {
int j ;
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:13 +03:00
edac_mc_dump_csrow ( & mci - > csrows [ i ] ) ;
for ( j = 0 ; j < mci - > csrows [ i ] . nr_channels ; j + + )
2006-03-26 13:38:52 +04:00
edac_mc_dump_channel (
& mci - > csrows [ i ] . channels [ j ] ) ;
2006-01-19 04:44:13 +03:00
}
}
# endif
down ( & mem_ctls_mutex ) ;
if ( add_mc_to_global_list ( mci ) )
2006-03-26 13:38:47 +04:00
goto fail0 ;
2006-01-19 04:44:13 +03:00
/* set load time so that error rate can be tracked */
mci - > start_time = jiffies ;
2007-02-12 11:53:08 +03:00
if ( edac_create_sysfs_mci_device ( mci ) ) {
edac_mc_printk ( mci , KERN_WARNING ,
2006-03-26 13:38:40 +04:00
" failed to create sysfs device \n " ) ;
2007-02-12 11:53:08 +03:00
goto fail1 ;
}
2006-01-19 04:44:13 +03:00
/* Report action taken */
2006-06-30 12:56:07 +04:00
edac_mc_printk ( mci , KERN_INFO , " Giving out device to %s %s: DEV %s \n " ,
mci - > mod_name , mci - > ctl_name , dev_name ( mci - > dev ) ) ;
2006-01-19 04:44:13 +03:00
2006-03-26 13:38:47 +04:00
up ( & mem_ctls_mutex ) ;
return 0 ;
2006-01-19 04:44:13 +03:00
2006-03-26 13:38:47 +04:00
fail1 :
del_mc_from_global_list ( mci ) ;
fail0 :
2006-01-19 04:44:13 +03:00
up ( & mem_ctls_mutex ) ;
2006-03-26 13:38:47 +04:00
return 1 ;
2006-01-19 04:44:13 +03:00
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_add_mc ) ;
2006-01-19 04:44:13 +03:00
/**
2006-03-26 13:38:49 +04:00
* edac_mc_del_mc : Remove sysfs entries for specified mci structure and
* remove mci structure from global list
2006-06-30 12:56:07 +04:00
* @ pdev : Pointer to ' struct device ' representing mci structure to remove .
2006-01-19 04:44:13 +03:00
*
2006-03-26 13:38:50 +04:00
* Return pointer to removed mci structure , or NULL if device not found .
2006-01-19 04:44:13 +03:00
*/
2006-06-30 12:56:07 +04:00
struct mem_ctl_info * edac_mc_del_mc ( struct device * dev )
2006-01-19 04:44:13 +03:00
{
2006-03-26 13:38:50 +04:00
struct mem_ctl_info * mci ;
2006-01-19 04:44:13 +03:00
2006-03-26 13:38:50 +04:00
debugf0 ( " MC: %s() \n " , __func__ ) ;
2006-01-19 04:44:13 +03:00
down ( & mem_ctls_mutex ) ;
2006-03-26 13:38:50 +04:00
2006-06-30 12:56:07 +04:00
if ( ( mci = find_mci_by_dev ( dev ) ) = = NULL ) {
2006-03-26 13:38:50 +04:00
up ( & mem_ctls_mutex ) ;
return NULL ;
}
edac_remove_sysfs_mci_device ( mci ) ;
2006-01-19 04:44:13 +03:00
del_mc_from_global_list ( mci ) ;
2006-03-26 13:38:50 +04:00
up ( & mem_ctls_mutex ) ;
2006-03-26 13:38:40 +04:00
edac_printk ( KERN_INFO , EDAC_MC ,
2006-06-30 12:56:07 +04:00
" Removed device %d for %s %s: DEV %s \n " , mci - > mc_idx ,
mci - > mod_name , mci - > ctl_name , dev_name ( mci - > dev ) ) ;
2006-03-26 13:38:50 +04:00
return mci ;
2006-01-19 04:44:13 +03:00
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_del_mc ) ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:49:32 +04:00
static void edac_mc_scrub_block ( unsigned long page , unsigned long offset ,
u32 size )
2006-01-19 04:44:13 +03:00
{
struct page * pg ;
void * virt_addr ;
unsigned long flags = 0 ;
2006-03-26 13:38:40 +04:00
debugf3 ( " %s() \n " , __func__ ) ;
2006-01-19 04:44:13 +03:00
/* ECC error page was not in our memory. Ignore it. */
if ( ! pfn_valid ( page ) )
return ;
/* Find the actual page structure then map it and fix */
pg = pfn_to_page ( page ) ;
if ( PageHighMem ( pg ) )
local_irq_save ( flags ) ;
virt_addr = kmap_atomic ( pg , KM_BOUNCE_READ ) ;
/* Perform architecture specific atomic scrub operation */
atomic_scrub ( virt_addr + offset , size ) ;
/* Unmap and complete */
kunmap_atomic ( virt_addr , KM_BOUNCE_READ ) ;
if ( PageHighMem ( pg ) )
local_irq_restore ( flags ) ;
}
/* FIXME - should return -1 */
2006-03-26 13:38:52 +04:00
int edac_mc_find_csrow_by_page ( struct mem_ctl_info * mci , unsigned long page )
2006-01-19 04:44:13 +03:00
{
struct csrow_info * csrows = mci - > csrows ;
int row , i ;
2006-03-26 13:38:40 +04:00
debugf1 ( " MC%d: %s(): 0x%lx \n " , mci - > mc_idx , __func__ , page ) ;
2006-01-19 04:44:13 +03:00
row = - 1 ;
for ( i = 0 ; i < mci - > nr_csrows ; i + + ) {
struct csrow_info * csrow = & csrows [ i ] ;
if ( csrow - > nr_pages = = 0 )
continue ;
2006-03-26 13:38:40 +04:00
debugf3 ( " MC%d: %s(): first(0x%lx) page(0x%lx) last(0x%lx) "
" mask(0x%lx) \n " , mci - > mc_idx , __func__ ,
csrow - > first_page , page , csrow - > last_page ,
csrow - > page_mask ) ;
2006-01-19 04:44:13 +03:00
if ( ( page > = csrow - > first_page ) & &
( page < = csrow - > last_page ) & &
( ( page & csrow - > page_mask ) = =
( csrow - > first_page & csrow - > page_mask ) ) ) {
row = i ;
break ;
}
}
if ( row = = - 1 )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_ERR ,
" could not look up page error address %lx \n " ,
( unsigned long ) page ) ;
2006-01-19 04:44:13 +03:00
return row ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_find_csrow_by_page ) ;
2006-01-19 04:44:13 +03:00
/* FIXME - setable log (warning/emerg) levels */
/* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */
void edac_mc_handle_ce ( struct mem_ctl_info * mci ,
2006-03-26 13:38:52 +04:00
unsigned long page_frame_number , unsigned long offset_in_page ,
unsigned long syndrome , int row , int channel , const char * msg )
2006-01-19 04:44:13 +03:00
{
unsigned long remapped_page ;
2006-03-26 13:38:40 +04:00
debugf3 ( " MC%d: %s() \n " , mci - > mc_idx , __func__ ) ;
2006-01-19 04:44:13 +03:00
/* FIXME - maybe make panic on INTERNAL ERROR an option */
if ( row > = mci - > nr_csrows | | row < 0 ) {
/* something is wrong */
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: row out of range "
" (%d >= %d) \n " , row , mci - > nr_csrows ) ;
2006-01-19 04:44:13 +03:00
edac_mc_handle_ce_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:13 +03:00
if ( channel > = mci - > csrows [ row ] . nr_channels | | channel < 0 ) {
/* something is wrong */
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: channel out of range "
" (%d >= %d) \n " , channel ,
mci - > csrows [ row ] . nr_channels ) ;
2006-01-19 04:44:13 +03:00
edac_mc_handle_ce_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ce ( ) )
2006-01-19 04:44:13 +03:00
/* FIXME - put in DIMM location */
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_WARNING ,
" CE page 0x%lx, offset 0x%lx, grain %d, syndrome "
" 0x%lx, row %d, channel %d, label \" %s \" : %s \n " ,
page_frame_number , offset_in_page ,
mci - > csrows [ row ] . grain , syndrome , row , channel ,
mci - > csrows [ row ] . channels [ channel ] . label , msg ) ;
2006-01-19 04:44:13 +03:00
mci - > ce_count + + ;
mci - > csrows [ row ] . ce_count + + ;
mci - > csrows [ row ] . channels [ channel ] . ce_count + + ;
if ( mci - > scrub_mode & SCRUB_SW_SRC ) {
/*
* Some MC ' s can remap memory so that it is still available
* at a different address when PCI devices map into memory .
* MC ' s that can ' t do this lose the memory where PCI devices
* are mapped . This mapping is MC dependant and so we call
* back into the MC driver for it to map the MC page to
* a physical ( CPU ) page which can then be mapped to a virtual
* page - which can then be scrubbed .
*/
remapped_page = mci - > ctl_page_to_phys ?
mci - > ctl_page_to_phys ( mci , page_frame_number ) :
page_frame_number ;
edac_mc_scrub_block ( remapped_page , offset_in_page ,
2006-03-26 13:38:52 +04:00
mci - > csrows [ row ] . grain ) ;
2006-01-19 04:44:13 +03:00
}
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_handle_ce ) ;
2006-01-19 04:44:13 +03:00
2006-03-26 13:38:52 +04:00
void edac_mc_handle_ce_no_info ( struct mem_ctl_info * mci , const char * msg )
2006-01-19 04:44:13 +03:00
{
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ce ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_WARNING ,
" CE - no information available: %s \n " , msg ) ;
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:13 +03:00
mci - > ce_noinfo_count + + ;
mci - > ce_count + + ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_handle_ce_no_info ) ;
2006-01-19 04:44:13 +03:00
void edac_mc_handle_ue ( struct mem_ctl_info * mci ,
2006-03-26 13:38:52 +04:00
unsigned long page_frame_number , unsigned long offset_in_page ,
int row , const char * msg )
2006-01-19 04:44:13 +03:00
{
int len = EDAC_MC_LABEL_LEN * 4 ;
char labels [ len + 1 ] ;
char * pos = labels ;
int chan ;
int chars ;
2006-03-26 13:38:40 +04:00
debugf3 ( " MC%d: %s() \n " , mci - > mc_idx , __func__ ) ;
2006-01-19 04:44:13 +03:00
/* FIXME - maybe make panic on INTERNAL ERROR an option */
if ( row > = mci - > nr_csrows | | row < 0 ) {
/* something is wrong */
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: row out of range "
" (%d >= %d) \n " , row , mci - > nr_csrows ) ;
2006-01-19 04:44:13 +03:00
edac_mc_handle_ue_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
chars = snprintf ( pos , len + 1 , " %s " ,
2006-03-26 13:38:52 +04:00
mci - > csrows [ row ] . channels [ 0 ] . label ) ;
2006-01-19 04:44:13 +03:00
len - = chars ;
pos + = chars ;
2006-03-26 13:38:52 +04:00
2006-01-19 04:44:13 +03:00
for ( chan = 1 ; ( chan < mci - > csrows [ row ] . nr_channels ) & & ( len > 0 ) ;
chan + + ) {
chars = snprintf ( pos , len + 1 , " :%s " ,
2006-03-26 13:38:52 +04:00
mci - > csrows [ row ] . channels [ chan ] . label ) ;
2006-01-19 04:44:13 +03:00
len - = chars ;
pos + = chars ;
}
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ue ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_EMERG ,
" UE page 0x%lx, offset 0x%lx, grain %d, row %d, "
" labels \" %s \" : %s \n " , page_frame_number ,
offset_in_page , mci - > csrows [ row ] . grain , row , labels ,
msg ) ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:49:33 +04:00
if ( edac_get_panic_on_ue ( ) )
2006-03-26 13:38:52 +04:00
panic ( " EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, "
" row %d, labels \" %s \" : %s \n " , mci - > mc_idx ,
page_frame_number , offset_in_page ,
mci - > csrows [ row ] . grain , row , labels , msg ) ;
2006-01-19 04:44:13 +03:00
mci - > ue_count + + ;
mci - > csrows [ row ] . ue_count + + ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_handle_ue ) ;
2006-01-19 04:44:13 +03:00
2006-03-26 13:38:52 +04:00
void edac_mc_handle_ue_no_info ( struct mem_ctl_info * mci , const char * msg )
2006-01-19 04:44:13 +03:00
{
2007-07-19 12:49:33 +04:00
if ( edac_get_panic_on_ue ( ) )
2006-01-19 04:44:13 +03:00
panic ( " EDAC MC%d: Uncorrected Error " , mci - > mc_idx ) ;
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ue ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_WARNING ,
" UE - no information available: %s \n " , msg ) ;
2006-01-19 04:44:13 +03:00
mci - > ue_noinfo_count + + ;
mci - > ue_count + + ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_handle_ue_no_info ) ;
2006-01-19 04:44:13 +03:00
2007-02-12 11:53:08 +03:00
/*************************************************************
* On Fully Buffered DIMM modules , this help function is
* called to process UE events
*/
void edac_mc_handle_fbd_ue ( struct mem_ctl_info * mci ,
unsigned int csrow ,
unsigned int channela ,
unsigned int channelb ,
char * msg )
{
int len = EDAC_MC_LABEL_LEN * 4 ;
char labels [ len + 1 ] ;
char * pos = labels ;
int chars ;
if ( csrow > = mci - > nr_csrows ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: row out of range (%d >= %d) \n " ,
csrow , mci - > nr_csrows ) ;
edac_mc_handle_ue_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
if ( channela > = mci - > csrows [ csrow ] . nr_channels ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: channel-a out of range "
" (%d >= %d) \n " ,
channela , mci - > csrows [ csrow ] . nr_channels ) ;
edac_mc_handle_ue_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
if ( channelb > = mci - > csrows [ csrow ] . nr_channels ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: channel-b out of range "
" (%d >= %d) \n " ,
channelb , mci - > csrows [ csrow ] . nr_channels ) ;
edac_mc_handle_ue_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
mci - > ue_count + + ;
mci - > csrows [ csrow ] . ue_count + + ;
/* Generate the DIMM labels from the specified channels */
chars = snprintf ( pos , len + 1 , " %s " ,
mci - > csrows [ csrow ] . channels [ channela ] . label ) ;
len - = chars ; pos + = chars ;
chars = snprintf ( pos , len + 1 , " -%s " ,
mci - > csrows [ csrow ] . channels [ channelb ] . label ) ;
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ue ( ) )
2007-02-12 11:53:08 +03:00
edac_mc_printk ( mci , KERN_EMERG ,
" UE row %d, channel-a= %d channel-b= %d "
" labels \" %s \" : %s \n " , csrow , channela , channelb ,
labels , msg ) ;
2007-07-19 12:49:33 +04:00
if ( edac_get_panic_on_ue ( ) )
2007-02-12 11:53:08 +03:00
panic ( " UE row %d, channel-a= %d channel-b= %d "
" labels \" %s \" : %s \n " , csrow , channela ,
channelb , labels , msg ) ;
}
EXPORT_SYMBOL ( edac_mc_handle_fbd_ue ) ;
/*************************************************************
* On Fully Buffered DIMM modules , this help function is
* called to process CE events
*/
void edac_mc_handle_fbd_ce ( struct mem_ctl_info * mci ,
unsigned int csrow ,
unsigned int channel ,
char * msg )
{
/* Ensure boundary values */
if ( csrow > = mci - > nr_csrows ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: row out of range (%d >= %d) \n " ,
csrow , mci - > nr_csrows ) ;
edac_mc_handle_ce_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
if ( channel > = mci - > csrows [ csrow ] . nr_channels ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
" INTERNAL ERROR: channel out of range (%d >= %d) \n " ,
channel , mci - > csrows [ csrow ] . nr_channels ) ;
edac_mc_handle_ce_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
2007-07-19 12:49:33 +04:00
if ( edac_get_log_ce ( ) )
2007-02-12 11:53:08 +03:00
/* FIXME - put in DIMM location */
edac_mc_printk ( mci , KERN_WARNING ,
" CE row %d, channel %d, label \" %s \" : %s \n " ,
csrow , channel ,
mci - > csrows [ csrow ] . channels [ channel ] . label ,
msg ) ;
mci - > ce_count + + ;
mci - > csrows [ csrow ] . ce_count + + ;
mci - > csrows [ csrow ] . channels [ channel ] . ce_count + + ;
}
EXPORT_SYMBOL ( edac_mc_handle_fbd_ce ) ;
2006-01-19 04:44:13 +03:00
/*
* Iterate over all MC instances and check for ECC , et al , errors
*/
2007-07-19 12:49:33 +04:00
void edac_check_mc_devices ( void )
2006-01-19 04:44:13 +03:00
{
struct list_head * item ;
struct mem_ctl_info * mci ;
2006-03-26 13:38:40 +04:00
debugf3 ( " %s() \n " , __func__ ) ;
2006-03-26 13:38:50 +04:00
down ( & mem_ctls_mutex ) ;
2006-01-19 04:44:13 +03:00
list_for_each ( item , & mc_devices ) {
mci = list_entry ( item , struct mem_ctl_info , link ) ;
if ( mci - > edac_check ! = NULL )
mci - > edac_check ( mci ) ;
}
2006-03-26 13:38:50 +04:00
up ( & mem_ctls_mutex ) ;
2006-01-19 04:44:13 +03:00
}