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>
2007-07-19 12:49:46 +04:00
# include <linux/edac.h>
2006-01-19 04:44:13 +03:00
# include <asm/uaccess.h>
# include <asm/page.h>
# include <asm/edac.h>
2007-07-19 12:49:47 +04:00
# include "edac_core.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 */
2007-07-19 12:49:38 +04:00
static DEFINE_MUTEX ( mem_ctls_mutex ) ;
2006-01-19 04:44:13 +03:00
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 ) ;
2007-07-19 12:49:58 +04:00
debugf4 ( " \t csrow->first_page = 0x%lx \n " , csrow - > first_page ) ;
2006-01-19 04:44:13 +03:00
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 ) ;
2007-07-19 12:49:58 +04:00
debugf4 ( " \t csrow->nr_channels = %d \n " , csrow - > nr_channels ) ;
2006-01-19 04:44:13 +03:00
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 ) ;
2007-07-19 12:49:58 +04:00
debugf3 ( " \t mod_name:ctl_name = %s:%s \n " , mci - > mod_name , mci - > ctl_name ) ;
2006-01-19 04:44:13 +03:00
debugf3 ( " \t pvt_info = %p \n \n " , mci - > pvt_info ) ;
}
2007-07-19 12:49:58 +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:50:21 +04:00
void * 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
2007-07-19 12:49:58 +04:00
return ( char * ) ptr ;
2006-01-19 04:44:13 +03:00
r = size % align ;
if ( r = = 0 )
2007-07-19 12:49:58 +04:00
return ( char * ) ptr ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:50:21 +04:00
return ( void * ) ( ( ( unsigned long ) ptr ) + align - r ) ;
2006-01-19 04:44:13 +03:00
}
/**
* 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 ,
2007-07-19 12:50:26 +04:00
unsigned nr_chans , int edac_index )
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 ;
2007-07-19 12:50:27 +04:00
int err ;
2006-01-19 04:44:13 +03:00
/* 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 .
*/
2007-07-19 12:49:58 +04:00
mci = ( struct mem_ctl_info * ) 0 ;
2007-07-19 12:50:21 +04:00
csi = edac_align_ptr ( & mci [ 1 ] , sizeof ( * csi ) ) ;
chi = edac_align_ptr ( & csi [ nr_csrows ] , sizeof ( * chi ) ) ;
2007-07-19 12:49:36 +04:00
pvt = edac_align_ptr ( & chi [ nr_chans * nr_csrows ] , sz_pvt ) ;
2007-07-19 12:49:58 +04:00
size = ( ( unsigned long ) pvt ) + sz_pvt ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:50:27 +04:00
mci = kzalloc ( size , GFP_KERNEL ) ;
if ( mci = = NULL )
2006-01-19 04:44:13 +03:00
return NULL ;
/* Adjust pointers so they point within the memory we just allocated
* rather than an imaginary chunk of memory located at address 0.
*/
2007-07-19 12:49:58 +04:00
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-01-19 04:44:13 +03:00
2007-07-19 12:50:26 +04:00
/* setup index and various internal pointers */
mci - > mc_idx = edac_index ;
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 ;
}
}
2007-07-19 12:49:52 +04:00
mci - > op_state = OP_ALLOC ;
2007-07-19 12:50:27 +04:00
/*
* Initialize the ' root ' kobj for the edac_mc controller
*/
err = edac_mc_register_sysfs_main_kobj ( mci ) ;
if ( err ) {
kfree ( mci ) ;
return NULL ;
}
/* at this point, the root kobj is valid, and in order to
* ' free ' the object , then the function :
* edac_mc_unregister_sysfs_main_kobj ( ) must be called
* which will perform kobj unregistration and the actual free
* will occur during the kobject callback operation
*/
2006-01-19 04:44:13 +03:00
return mci ;
}
2006-03-26 13:38:55 +04:00
EXPORT_SYMBOL_GPL ( edac_mc_alloc ) ;
2006-01-19 04:44:13 +03:00
/**
2007-07-19 12:50:27 +04:00
* edac_mc_free
* ' Free ' a previously allocated ' mci ' structure
2006-01-19 04:44:13 +03:00
* @ mci : pointer to a struct mem_ctl_info structure
*/
void edac_mc_free ( struct mem_ctl_info * mci )
{
2007-07-19 12:50:27 +04:00
edac_mc_unregister_sysfs_main_kobj ( 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 ;
}
2007-07-19 12:49:52 +04:00
/*
* handler for EDAC to check if NMI type handler has asserted interrupt
*/
static int edac_mc_assert_error_check_and_clear ( void )
{
2007-07-19 12:49:54 +04:00
int old_state ;
2007-07-19 12:49:52 +04:00
2007-07-19 12:49:58 +04:00
if ( edac_op_state = = EDAC_OPSTATE_POLL )
2007-07-19 12:49:52 +04:00
return 1 ;
2007-07-19 12:49:54 +04:00
old_state = edac_err_assert ;
edac_err_assert = 0 ;
2007-07-19 12:49:52 +04:00
2007-07-19 12:49:54 +04:00
return old_state ;
2007-07-19 12:49:52 +04:00
}
/*
* edac_mc_workq_function
* performs the operation scheduled by a workq request
*/
static void edac_mc_workq_function ( struct work_struct * work_req )
{
2007-07-19 12:49:58 +04:00
struct delayed_work * d_work = ( struct delayed_work * ) work_req ;
2007-07-19 12:49:52 +04:00
struct mem_ctl_info * mci = to_edac_mem_ctl_work ( d_work ) ;
mutex_lock ( & mem_ctls_mutex ) ;
/* Only poll controllers that are running polled and have a check */
if ( edac_mc_assert_error_check_and_clear ( ) & & ( mci - > edac_check ! = NULL ) )
mci - > edac_check ( mci ) ;
/*
* FIXME : temp place holder for PCI checks ,
* goes away when we break out PCI
*/
edac_pci_do_parity_check ( ) ;
mutex_unlock ( & mem_ctls_mutex ) ;
/* Reschedule */
2007-07-19 12:49:54 +04:00
queue_delayed_work ( edac_workqueue , & mci - > work ,
2007-07-19 12:50:13 +04:00
msecs_to_jiffies ( edac_mc_get_poll_msec ( ) ) ) ;
2007-07-19 12:49:52 +04:00
}
/*
* edac_mc_workq_setup
* initialize a workq item for this mci
* passing in the new delay period in msec
*/
void edac_mc_workq_setup ( struct mem_ctl_info * mci , unsigned msec )
{
debugf0 ( " %s() \n " , __func__ ) ;
INIT_DELAYED_WORK ( & mci - > work , edac_mc_workq_function ) ;
queue_delayed_work ( edac_workqueue , & mci - > work , msecs_to_jiffies ( msec ) ) ;
}
/*
* edac_mc_workq_teardown
* stop the workq processing on this mci
*/
void edac_mc_workq_teardown ( struct mem_ctl_info * mci )
{
int status ;
status = cancel_delayed_work ( & mci - > work ) ;
if ( status = = 0 ) {
/* workq instance might be running, wait for it */
flush_workqueue ( edac_workqueue ) ;
}
}
/*
* edac_reset_delay_period
*/
void edac_reset_delay_period ( struct mem_ctl_info * mci , unsigned long value )
{
mutex_lock ( & mem_ctls_mutex ) ;
/* cancel the current workq request */
edac_mc_workq_teardown ( mci ) ;
/* restart the workq request, with new delay value */
edac_mc_workq_setup ( mci , value ) ;
mutex_unlock ( & mem_ctls_mutex ) ;
}
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 .
*/
2007-07-19 12:49:58 +04:00
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 ) ;
2007-07-19 12:49:46 +04:00
atomic_inc ( & edac_handlers ) ;
2006-01-19 04:44:13 +03:00
return 0 ;
2006-06-30 12:56:08 +04:00
2007-07-19 12:50:13 +04:00
fail0 :
2006-06-30 12:56:08 +04:00
edac_printk ( KERN_WARNING , EDAC_MC ,
2007-07-19 12:50:13 +04:00
" %s (%s) %s %s already assigned %d \n " , p - > dev - > bus_id ,
dev_name ( mci ) , p - > mod_name , p - > ctl_name , p - > mc_idx ) ;
2006-06-30 12:56:08 +04:00
return 1 ;
2007-07-19 12:50:13 +04:00
fail1 :
2006-06-30 12:56:08 +04:00
edac_printk ( KERN_WARNING , EDAC_MC ,
2007-07-19 12:50:13 +04:00
" bug in low-level driver: attempt to assign \n "
" duplicate mc_idx %d in %s() \n " , p - > mc_idx , __func__ ) ;
2006-06-30 12:56:08 +04:00
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
{
2007-07-19 12:49:46 +04:00
atomic_dec ( & edac_handlers ) ;
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 .
*/
2007-07-19 12:49:58 +04:00
struct mem_ctl_info * edac_mc_find ( int idx )
2007-07-19 12:49:31 +04:00
{
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? */
2007-07-19 12:50:26 +04:00
int edac_mc_add_mc ( struct mem_ctl_info * mci )
2006-01-19 04:44:13 +03:00
{
2006-03-26 13:38:40 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:50:26 +04:00
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 + + )
2007-07-19 12:49:58 +04:00
edac_mc_dump_channel ( & mci - > csrows [ i ] .
2007-07-19 12:50:13 +04:00
channels [ j ] ) ;
2006-01-19 04:44:13 +03:00
}
}
# endif
2007-07-19 12:49:38 +04:00
mutex_lock ( & mem_ctls_mutex ) ;
2006-01-19 04:44:13 +03:00
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 ,
2007-07-19 12:50:13 +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
2007-07-19 12:49:52 +04:00
/* If there IS a check routine, then we are running POLLED */
if ( mci - > edac_check ! = NULL ) {
/* This instance is NOW RUNNING */
mci - > op_state = OP_RUNNING_POLL ;
edac_mc_workq_setup ( mci , edac_mc_get_poll_msec ( ) ) ;
} else {
mci - > op_state = OP_RUNNING_INTERRUPT ;
}
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 " ,
2007-07-19 12:50:13 +04:00
mci - > mod_name , mci - > ctl_name , dev_name ( mci ) ) ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:49:38 +04:00
mutex_unlock ( & mem_ctls_mutex ) ;
2006-03-26 13:38:47 +04:00
return 0 ;
2006-01-19 04:44:13 +03:00
2007-07-19 12:50:13 +04:00
fail1 :
2006-03-26 13:38:47 +04:00
del_mc_from_global_list ( mci ) ;
2007-07-19 12:50:13 +04:00
fail0 :
2007-07-19 12:49:38 +04:00
mutex_unlock ( & 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
*/
2007-07-19 12:49:58 +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__ ) ;
2007-07-19 12:49:38 +04:00
mutex_lock ( & 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 ) {
2007-07-19 12:49:38 +04:00
mutex_unlock ( & mem_ctls_mutex ) ;
2006-03-26 13:38:50 +04:00
return NULL ;
}
2007-07-19 12:49:52 +04:00
/* marking MCI offline */
mci - > op_state = OP_OFFLINE ;
/* flush workq processes */
edac_mc_workq_teardown ( mci ) ;
2006-03-26 13:38:50 +04:00
edac_remove_sysfs_mci_device ( mci ) ;
2006-01-19 04:44:13 +03:00
del_mc_from_global_list ( mci ) ;
2007-07-19 12:49:38 +04:00
mutex_unlock ( & mem_ctls_mutex ) ;
2006-03-26 13:38:40 +04:00
edac_printk ( KERN_INFO , EDAC_MC ,
2007-07-19 12:50:13 +04:00
" Removed device %d for %s %s: DEV %s \n " , mci - > mc_idx ,
mci - > mod_name , mci - > ctl_name , dev_name ( mci ) ) ;
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. */
2007-07-19 12:49:58 +04:00
if ( ! pfn_valid ( page ) )
2006-01-19 04:44:13 +03:00
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 ,
2007-07-19 12:50:13 +04:00
" 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 ,
2007-07-19 12:50:13 +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 ,
2007-07-19 12:50:13 +04:00
" 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 ,
2007-07-19 12:50:13 +04:00
" 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:54 +04:00
if ( edac_mc_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 ,
2007-07-19 12:50:13 +04:00
" 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 ?
2007-07-19 12:50:13 +04:00
mci - > ctl_page_to_phys ( mci , page_frame_number ) :
page_frame_number ;
2006-01-19 04:44:13 +03:00
edac_mc_scrub_block ( remapped_page , offset_in_page ,
2007-07-19 12:50:13 +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:54 +04:00
if ( edac_mc_get_log_ce ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_WARNING ,
2007-07-19 12:50:13 +04:00
" 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 ,
2007-07-19 12:50:13 +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 ,
2007-07-19 12:50:13 +04:00
" 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 " ,
2007-07-19 12:49:58 +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 ) ;
2007-07-19 12:50:13 +04:00
chan + + ) {
2006-01-19 04:44:13 +03:00
chars = snprintf ( pos , len + 1 , " :%s " ,
2007-07-19 12:49:58 +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:54 +04:00
if ( edac_mc_get_log_ue ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_EMERG ,
2007-07-19 12:50:13 +04:00
" 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:54 +04:00
if ( edac_mc_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, "
2007-07-19 12:50:13 +04:00
" 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:54 +04:00
if ( edac_mc_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:54 +04:00
if ( edac_mc_get_log_ue ( ) )
2006-03-26 13:38:40 +04:00
edac_mc_printk ( mci , KERN_WARNING ,
2007-07-19 12:50:13 +04:00
" UE - no information available: %s \n " , msg ) ;
2006-01-19 04:44:13 +03:00
mci - > ue_noinfo_count + + ;
mci - > ue_count + + ;
}
2007-07-19 12:49:58 +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 ,
2007-07-19 12:50:13 +04:00
unsigned int csrow ,
unsigned int channela ,
unsigned int channelb , char * msg )
2007-02-12 11:53:08 +03:00
{
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 ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: row out of range (%d >= %d) \n " ,
csrow , mci - > nr_csrows ) ;
2007-02-12 11:53:08 +03:00
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 ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: channel-a out of range "
" (%d >= %d) \n " ,
channela , mci - > csrows [ csrow ] . nr_channels ) ;
2007-02-12 11:53:08 +03:00
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 ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: channel-b out of range "
" (%d >= %d) \n " ,
channelb , mci - > csrows [ csrow ] . nr_channels ) ;
2007-02-12 11:53:08 +03:00
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 ) ;
2007-07-19 12:49:58 +04:00
len - = chars ;
pos + = chars ;
2007-02-12 11:53:08 +03:00
chars = snprintf ( pos , len + 1 , " -%s " ,
mci - > csrows [ csrow ] . channels [ channelb ] . label ) ;
2007-07-19 12:49:54 +04:00
if ( edac_mc_get_log_ue ( ) )
2007-02-12 11:53:08 +03:00
edac_mc_printk ( mci , KERN_EMERG ,
2007-07-19 12:50:13 +04:00
" UE row %d, channel-a= %d channel-b= %d "
" labels \" %s \" : %s \n " , csrow , channela , channelb ,
labels , msg ) ;
2007-02-12 11:53:08 +03:00
2007-07-19 12:49:54 +04:00
if ( edac_mc_get_panic_on_ue ( ) )
2007-02-12 11:53:08 +03:00
panic ( " UE row %d, channel-a= %d channel-b= %d "
2007-07-19 12:50:13 +04:00
" labels \" %s \" : %s \n " , csrow , channela ,
channelb , labels , msg ) ;
2007-02-12 11:53:08 +03:00
}
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 ,
2007-07-19 12:50:13 +04:00
unsigned int csrow , unsigned int channel , char * msg )
2007-02-12 11:53:08 +03:00
{
/* Ensure boundary values */
if ( csrow > = mci - > nr_csrows ) {
/* something is wrong */
edac_mc_printk ( mci , KERN_ERR ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: row out of range (%d >= %d) \n " ,
csrow , mci - > nr_csrows ) ;
2007-02-12 11:53:08 +03:00
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 ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: channel out of range (%d >= %d) \n " ,
channel , mci - > csrows [ csrow ] . nr_channels ) ;
2007-02-12 11:53:08 +03:00
edac_mc_handle_ce_no_info ( mci , " INTERNAL ERROR " ) ;
return ;
}
2007-07-19 12:49:54 +04:00
if ( edac_mc_get_log_ce ( ) )
2007-02-12 11:53:08 +03:00
/* FIXME - put in DIMM location */
edac_mc_printk ( mci , KERN_WARNING ,
2007-07-19 12:50:13 +04:00
" CE row %d, channel %d, label \" %s \" : %s \n " ,
csrow , channel ,
mci - > csrows [ csrow ] . channels [ channel ] . label , msg ) ;
2007-02-12 11:53:08 +03:00
mci - > ce_count + + ;
mci - > csrows [ csrow ] . ce_count + + ;
mci - > csrows [ csrow ] . channels [ channel ] . ce_count + + ;
}
2007-07-19 12:49:58 +04:00
EXPORT_SYMBOL ( edac_mc_handle_fbd_ce ) ;
2007-02-12 11:53:08 +03:00
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__ ) ;
2007-07-19 12:49:38 +04:00
mutex_lock ( & 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 ) ;
}
2007-07-19 12:49:38 +04:00
mutex_unlock ( & mem_ctls_mutex ) ;
2006-01-19 04:44:13 +03:00
}