2007-07-19 12:49:36 +04:00
/*
* edac_device . c
* ( C ) 2007 www . douglaskthompson . com
*
* This file may be distributed under the terms of the
* GNU General Public License .
*
* Written by Doug Thompson < norsk5 @ xmission . com >
*
* edac_device API implementation
* 19 Jan 2007
*/
# include <linux/module.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>
2007-07-19 12:50:20 +04:00
# include <linux/jiffies.h>
2007-07-19 12:49:36 +04:00
# include <linux/spinlock.h>
# include <linux/list.h>
# include <linux/sysdev.h>
# include <linux/ctype.h>
# include <linux/workqueue.h>
# include <asm/uaccess.h>
# include <asm/page.h>
# include "edac_core.h"
# include "edac_module.h"
2007-07-19 12:50:30 +04:00
/* lock for the list: 'edac_device_list', manipulation of this list
* is protected by the ' device_ctls_mutex ' lock
*/
2007-07-19 12:50:22 +04:00
static DEFINE_MUTEX ( device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
static struct list_head edac_device_list = LIST_HEAD_INIT ( edac_device_list ) ;
# ifdef CONFIG_EDAC_DEBUG
static void edac_device_dump_device ( struct edac_device_ctl_info * edac_dev )
{
2007-07-19 12:49:58 +04:00
debugf3 ( " \t edac_dev = %p dev_idx=%d \n " , edac_dev , edac_dev - > dev_idx ) ;
2007-07-19 12:49:36 +04:00
debugf4 ( " \t edac_dev->edac_check = %p \n " , edac_dev - > edac_check ) ;
debugf3 ( " \t dev = %p \n " , edac_dev - > dev ) ;
debugf3 ( " \t mod_name:ctl_name = %s:%s \n " ,
edac_dev - > mod_name , edac_dev - > ctl_name ) ;
debugf3 ( " \t pvt_info = %p \n \n " , edac_dev - > pvt_info ) ;
}
2007-07-19 12:49:58 +04:00
# endif /* CONFIG_EDAC_DEBUG */
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:29 +04:00
2007-07-19 12:49:36 +04:00
/*
2007-07-19 12:50:20 +04:00
* edac_device_alloc_ctl_info ( )
* Allocate a new edac device control info structure
*
* The control structure is allocated in complete chunk
* from the OS . It is in turn sub allocated to the
* various objects that compose the struture
*
* The structure has a ' nr_instance ' array within itself .
* Each instance represents a major component
* Example : L1 cache and L2 cache are 2 instance components
*
* Within each instance is an array of ' nr_blocks ' blockoffsets
2007-07-19 12:49:36 +04:00
*/
struct edac_device_ctl_info * edac_device_alloc_ctl_info (
unsigned sz_private ,
2007-07-19 12:50:20 +04:00
char * edac_device_name , unsigned nr_instances ,
char * edac_block_name , unsigned nr_blocks ,
unsigned offset_value , /* zero, 1, or other based offset */
2007-07-19 12:50:27 +04:00
struct edac_dev_sysfs_block_attribute * attrib_spec , unsigned nr_attrib ,
int device_index )
2007-07-19 12:49:36 +04:00
{
struct edac_device_ctl_info * dev_ctl ;
struct edac_device_instance * dev_inst , * inst ;
struct edac_device_block * dev_blk , * blk_p , * blk ;
2007-07-19 12:50:25 +04:00
struct edac_dev_sysfs_block_attribute * dev_attrib , * attrib_p , * attrib ;
2007-07-19 12:49:36 +04:00
unsigned total_size ;
unsigned count ;
unsigned instance , block , attr ;
void * pvt ;
2007-07-19 12:50:29 +04:00
int err ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:33 +04:00
debugf4 ( " %s() instances=%d blocks=%d \n " ,
2007-07-19 12:49:58 +04:00
__func__ , nr_instances , nr_blocks ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Calculate the size of memory we need to allocate AND
* determine the offsets of the various item arrays
* ( instance , block , attrib ) from the start of an allocated structure .
* We want the alignment of each item ( instance , block , attrib )
2007-07-19 12:49:36 +04:00
* 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:50:20 +04:00
dev_ctl = ( struct edac_device_ctl_info * ) NULL ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Calc the 'end' offset past end of ONE ctl_info structure
* which will become the start of the ' instance ' array
*/
2007-07-19 12:50:21 +04:00
dev_inst = edac_align_ptr ( & dev_ctl [ 1 ] , sizeof ( * dev_inst ) ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Calc the 'end' offset past the instance array within the ctl_info
* which will become the start of the block array
*/
2007-07-19 12:50:21 +04:00
dev_blk = edac_align_ptr ( & dev_inst [ nr_instances ] , sizeof ( * dev_blk ) ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Calc the 'end' offset past the dev_blk array
* which will become the start of the attrib array , if any .
*/
2007-07-19 12:49:36 +04:00
count = nr_instances * nr_blocks ;
2007-07-19 12:50:21 +04:00
dev_attrib = edac_align_ptr ( & dev_blk [ count ] , sizeof ( * dev_attrib ) ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Check for case of when an attribute array is specified */
if ( nr_attrib > 0 ) {
/* calc how many nr_attrib we need */
count * = nr_attrib ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Calc the 'end' offset past the attributes array */
pvt = edac_align_ptr ( & dev_attrib [ count ] , sz_private ) ;
} else {
/* no attribute array specificed */
pvt = edac_align_ptr ( dev_attrib , sz_private ) ;
}
/* 'pvt' now points to where the private data area is.
* At this point ' pvt ' ( like dev_inst , dev_blk and dev_attrib )
* is baselined at ZERO
*/
2007-07-19 12:49:58 +04:00
total_size = ( ( unsigned long ) pvt ) + sz_private ;
2007-07-19 12:49:36 +04:00
/* Allocate the amount of memory for the set of control structures */
2007-07-19 12:50:20 +04:00
dev_ctl = kzalloc ( total_size , GFP_KERNEL ) ;
if ( dev_ctl = = NULL )
2007-07-19 12:49:36 +04:00
return NULL ;
2007-07-19 12:50:25 +04:00
/* Adjust pointers so they point within the actual memory we
* just allocated rather than an imaginary chunk of memory
* located at address 0.
* ' dev_ctl ' points to REAL memory , while the others are
* ZERO based and thus need to be adjusted to point within
* the allocated memory .
2007-07-19 12:49:36 +04:00
*/
dev_inst = ( struct edac_device_instance * )
2007-07-19 12:50:13 +04:00
( ( ( char * ) dev_ctl ) + ( ( unsigned long ) dev_inst ) ) ;
2007-07-19 12:49:36 +04:00
dev_blk = ( struct edac_device_block * )
2007-07-19 12:50:13 +04:00
( ( ( char * ) dev_ctl ) + ( ( unsigned long ) dev_blk ) ) ;
2007-07-19 12:50:25 +04:00
dev_attrib = ( struct edac_dev_sysfs_block_attribute * )
2007-07-19 12:50:13 +04:00
( ( ( char * ) dev_ctl ) + ( ( unsigned long ) dev_attrib ) ) ;
2007-07-19 12:49:58 +04:00
pvt = sz_private ? ( ( ( char * ) dev_ctl ) + ( ( unsigned long ) pvt ) ) : NULL ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* Begin storing the information into the control info structure */
2007-07-19 12:50:27 +04:00
dev_ctl - > dev_idx = device_index ;
2007-07-19 12:49:36 +04:00
dev_ctl - > nr_instances = nr_instances ;
dev_ctl - > instances = dev_inst ;
dev_ctl - > pvt_info = pvt ;
2008-02-07 11:14:51 +03:00
/* Default logging of CEs and UEs */
dev_ctl - > log_ce = 1 ;
dev_ctl - > log_ue = 1 ;
2007-07-19 12:50:20 +04:00
/* Name of this edac device */
snprintf ( dev_ctl - > name , sizeof ( dev_ctl - > name ) , " %s " , edac_device_name ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:33 +04:00
debugf4 ( " %s() edac_dev=%p next after end=%p \n " ,
__func__ , dev_ctl , pvt + sz_private ) ;
2007-07-19 12:49:36 +04:00
/* Initialize every Instance */
for ( instance = 0 ; instance < nr_instances ; instance + + ) {
inst = & dev_inst [ instance ] ;
inst - > ctl = dev_ctl ;
inst - > nr_blocks = nr_blocks ;
blk_p = & dev_blk [ instance * nr_blocks ] ;
inst - > blocks = blk_p ;
/* name of this instance */
snprintf ( inst - > name , sizeof ( inst - > name ) ,
2007-07-19 12:49:58 +04:00
" %s%u " , edac_device_name , instance ) ;
2007-07-19 12:49:36 +04:00
/* Initialize every block in each instance */
2007-07-19 12:49:58 +04:00
for ( block = 0 ; block < nr_blocks ; block + + ) {
2007-07-19 12:49:36 +04:00
blk = & blk_p [ block ] ;
blk - > instance = inst ;
snprintf ( blk - > name , sizeof ( blk - > name ) ,
2007-07-19 12:50:11 +04:00
" %s%d " , edac_block_name , block + offset_value ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:33 +04:00
debugf4 ( " %s() instance=%d inst_p=%p block=#%d "
" block_p=%p name='%s' \n " ,
__func__ , instance , inst , block ,
blk , blk - > name ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:25 +04:00
/* if there are NO attributes OR no attribute pointer
* then continue on to next block iteration
*/
if ( ( nr_attrib = = 0 ) | | ( attrib_spec = = NULL ) )
continue ;
/* setup the attribute array for this block */
blk - > nr_attribs = nr_attrib ;
attrib_p = & dev_attrib [ block * nr_instances * nr_attrib ] ;
blk - > block_attributes = attrib_p ;
2007-07-19 12:50:33 +04:00
debugf4 ( " %s() THIS BLOCK_ATTRIB=%p \n " ,
__func__ , blk - > block_attributes ) ;
2007-07-19 12:50:25 +04:00
/* Initialize every user specified attribute in this
* block with the data the caller passed in
2007-07-19 12:50:33 +04:00
* Each block gets its own copy of pointers ,
* and its unique ' value '
2007-07-19 12:50:25 +04:00
*/
for ( attr = 0 ; attr < nr_attrib ; attr + + ) {
attrib = & attrib_p [ attr ] ;
2007-07-19 12:50:33 +04:00
/* populate the unique per attrib
* with the code pointers and info
*/
attrib - > attr = attrib_spec [ attr ] . attr ;
attrib - > show = attrib_spec [ attr ] . show ;
attrib - > store = attrib_spec [ attr ] . store ;
attrib - > block = blk ; /* up link */
debugf4 ( " %s() alloc-attrib=%p attrib_name='%s' "
" attrib-spec=%p spec-name=%s \n " ,
__func__ , attrib , attrib - > attr . name ,
& attrib_spec [ attr ] ,
attrib_spec [ attr ] . attr . name
) ;
2007-07-19 12:49:36 +04:00
}
}
}
/* Mark this instance as merely ALLOCATED */
dev_ctl - > op_state = OP_ALLOC ;
2007-07-19 12:50:29 +04:00
/*
* Initialize the ' root ' kobj for the edac_device controller
*/
err = edac_device_register_sysfs_main_kobj ( dev_ctl ) ;
if ( err ) {
kfree ( dev_ctl ) ;
return NULL ;
}
/* at this point, the root kobj is valid, and in order to
* ' free ' the object , then the function :
* edac_device_unregister_sysfs_main_kobj ( ) must be called
* which will perform kobj unregistration and the actual free
* will occur during the kobject callback operation
*/
2007-07-19 12:49:36 +04:00
return dev_ctl ;
}
EXPORT_SYMBOL_GPL ( edac_device_alloc_ctl_info ) ;
/*
* edac_device_free_ctl_info ( )
* frees the memory allocated by the edac_device_alloc_ctl_info ( )
* function
*/
2007-07-19 12:49:58 +04:00
void edac_device_free_ctl_info ( struct edac_device_ctl_info * ctl_info )
{
2007-07-19 12:50:29 +04:00
edac_device_unregister_sysfs_main_kobj ( ctl_info ) ;
2007-07-19 12:49:36 +04:00
}
2007-07-19 12:49:58 +04:00
EXPORT_SYMBOL_GPL ( edac_device_free_ctl_info ) ;
2007-07-19 12:49:36 +04:00
/*
* find_edac_device_by_dev
* scans the edac_device list for a specific ' struct device * '
2007-07-19 12:50:20 +04:00
*
* lock to be held prior to call : device_ctls_mutex
*
* Return :
* pointer to control structure managing ' dev '
* NULL if not found on list
2007-07-19 12:49:36 +04:00
*/
2007-07-19 12:49:58 +04:00
static struct edac_device_ctl_info * find_edac_device_by_dev ( struct device * dev )
2007-07-19 12:49:36 +04:00
{
struct edac_device_ctl_info * edac_dev ;
struct list_head * item ;
2007-07-19 12:50:33 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:49:36 +04:00
list_for_each ( item , & edac_device_list ) {
edac_dev = list_entry ( item , struct edac_device_ctl_info , link ) ;
if ( edac_dev - > dev = = dev )
return edac_dev ;
}
return NULL ;
}
/*
* add_edac_dev_to_global_list
* Before calling this function , caller must
* assign a unique value to edac_dev - > dev_idx .
2007-07-19 12:50:20 +04:00
*
* lock to be held prior to call : device_ctls_mutex
*
2007-07-19 12:49:36 +04:00
* Return :
* 0 on success
* 1 on failure .
*/
2007-07-19 12:49:58 +04:00
static int add_edac_dev_to_global_list ( struct edac_device_ctl_info * edac_dev )
2007-07-19 12:49:36 +04:00
{
struct list_head * item , * insert_before ;
struct edac_device_ctl_info * rover ;
insert_before = & edac_device_list ;
/* Determine if already on the list */
2007-07-19 12:50:20 +04:00
rover = find_edac_device_by_dev ( edac_dev - > dev ) ;
if ( unlikely ( rover ! = NULL ) )
2007-07-19 12:49:36 +04:00
goto fail0 ;
/* Insert in ascending order by 'dev_idx', so find position */
list_for_each ( item , & edac_device_list ) {
rover = list_entry ( item , struct edac_device_ctl_info , link ) ;
if ( rover - > dev_idx > = edac_dev - > dev_idx ) {
if ( unlikely ( rover - > dev_idx = = edac_dev - > dev_idx ) )
goto fail1 ;
insert_before = item ;
break ;
}
}
list_add_tail_rcu ( & edac_dev - > link , insert_before ) ;
return 0 ;
2007-07-19 12:50:13 +04:00
fail0 :
2007-07-19 12:49:36 +04:00
edac_printk ( KERN_WARNING , EDAC_MC ,
2007-07-19 12:50:13 +04:00
" %s (%s) %s %s already assigned %d \n " ,
rover - > dev - > bus_id , dev_name ( rover ) ,
rover - > mod_name , rover - > ctl_name , rover - > dev_idx ) ;
2007-07-19 12:49:36 +04:00
return 1 ;
2007-07-19 12:50:13 +04:00
fail1 :
2007-07-19 12:49:36 +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 dev_idx %d in %s() \n " , rover - > dev_idx ,
__func__ ) ;
2007-07-19 12:49:36 +04:00
return 1 ;
}
/*
* complete_edac_device_list_del
2007-07-19 12:50:20 +04:00
*
* callback function when reference count is zero
2007-07-19 12:49:36 +04:00
*/
static void complete_edac_device_list_del ( struct rcu_head * head )
{
struct edac_device_ctl_info * edac_dev ;
edac_dev = container_of ( head , struct edac_device_ctl_info , rcu ) ;
INIT_LIST_HEAD ( & edac_dev - > link ) ;
2007-07-19 12:50:29 +04:00
complete ( & edac_dev - > removal_complete ) ;
2007-07-19 12:49:36 +04:00
}
/*
* del_edac_device_from_global_list
2007-07-19 12:50:20 +04:00
*
2007-07-19 12:50:29 +04:00
* remove the RCU , setup for a callback call ,
* then wait for the callback to occur
2007-07-19 12:49:36 +04:00
*/
2007-07-19 12:49:58 +04:00
static void del_edac_device_from_global_list ( struct edac_device_ctl_info
2007-07-19 12:50:13 +04:00
* edac_device )
2007-07-19 12:49:36 +04:00
{
list_del_rcu ( & edac_device - > link ) ;
2007-07-19 12:50:29 +04:00
init_completion ( & edac_device - > removal_complete ) ;
2007-07-19 12:49:36 +04:00
call_rcu ( & edac_device - > rcu , complete_edac_device_list_del ) ;
2007-07-19 12:50:29 +04:00
wait_for_completion ( & edac_device - > removal_complete ) ;
2007-07-19 12:49:36 +04:00
}
/**
* edac_device_find
* Search for a edac_device_ctl_info structure whose index is ' idx ' .
*
* If found , return a pointer to the structure .
* Else return NULL .
*
* Caller must hold device_ctls_mutex .
*/
2007-07-19 12:49:58 +04:00
struct edac_device_ctl_info * edac_device_find ( int idx )
2007-07-19 12:49:36 +04:00
{
struct list_head * item ;
struct edac_device_ctl_info * edac_dev ;
/* Iterate over list, looking for exact match of ID */
list_for_each ( item , & edac_device_list ) {
edac_dev = list_entry ( item , struct edac_device_ctl_info , link ) ;
if ( edac_dev - > dev_idx > = idx ) {
if ( edac_dev - > dev_idx = = idx )
return edac_dev ;
/* not on list, so terminate early */
break ;
}
}
return NULL ;
}
2007-07-19 12:50:20 +04:00
EXPORT_SYMBOL_GPL ( edac_device_find ) ;
2007-07-19 12:49:36 +04:00
/*
2007-07-19 12:49:52 +04:00
* edac_device_workq_function
2007-07-19 12:49:36 +04:00
* performs the operation scheduled by a workq request
2007-07-19 12:50:30 +04:00
*
* this workq is embedded within an edac_device_ctl_info
* structure , that needs to be polled for possible error events .
*
* This operation is to acquire the list mutex lock
* ( thus preventing insertation or deletion )
* and then call the device ' s poll function IFF this device is
* running polled and there is a poll function defined .
2007-07-19 12:49:36 +04:00
*/
2007-07-19 12:49:52 +04:00
static void edac_device_workq_function ( struct work_struct * work_req )
2007-07-19 12:49:36 +04:00
{
2007-07-19 12:49:58 +04:00
struct delayed_work * d_work = ( struct delayed_work * ) work_req ;
struct edac_device_ctl_info * edac_dev = to_edac_device_ctl_work ( d_work ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:22 +04:00
mutex_lock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
/* Only poll controllers that are running polled and have a check */
if ( ( edac_dev - > op_state = = OP_RUNNING_POLL ) & &
2007-07-19 12:50:13 +04:00
( edac_dev - > edac_check ! = NULL ) ) {
edac_dev - > edac_check ( edac_dev ) ;
2007-07-19 12:49:36 +04:00
}
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:30 +04:00
/* Reschedule the workq for the next time period to start again
* if the number of msec is for 1 sec , then adjust to the next
* whole one second to save timers fireing all over the period
* between integral seconds
*/
if ( edac_dev - > poll_msec = = 1000 )
queue_delayed_work ( edac_workqueue , & edac_dev - > work ,
2008-02-07 11:14:51 +03:00
round_jiffies_relative ( edac_dev - > delay ) ) ;
2007-07-19 12:50:30 +04:00
else
queue_delayed_work ( edac_workqueue , & edac_dev - > work ,
edac_dev - > delay ) ;
2007-07-19 12:49:36 +04:00
}
/*
2007-07-19 12:49:52 +04:00
* edac_device_workq_setup
2007-07-19 12:49:36 +04:00
* initialize a workq item for this edac_device instance
* passing in the new delay period in msec
*/
2007-07-19 12:49:52 +04:00
void edac_device_workq_setup ( struct edac_device_ctl_info * edac_dev ,
2007-07-19 12:50:13 +04:00
unsigned msec )
2007-07-19 12:49:36 +04:00
{
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:50:30 +04:00
/* take the arg 'msec' and set it into the control structure
* to used in the time period calculation
* then calc the number of jiffies that represents
*/
2007-07-19 12:49:36 +04:00
edac_dev - > poll_msec = msec ;
2007-07-19 12:50:30 +04:00
edac_dev - > delay = msecs_to_jiffies ( msec ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:49:52 +04:00
INIT_DELAYED_WORK ( & edac_dev - > work , edac_device_workq_function ) ;
2007-07-19 12:50:30 +04:00
/* optimize here for the 1 second case, which will be normal value, to
* fire ON the 1 second time event . This helps reduce all sorts of
* timers firing on sub - second basis , while they are happy
* to fire together on the 1 second exactly
*/
if ( edac_dev - > poll_msec = = 1000 )
queue_delayed_work ( edac_workqueue , & edac_dev - > work ,
2008-02-07 11:14:51 +03:00
round_jiffies_relative ( edac_dev - > delay ) ) ;
2007-07-19 12:50:30 +04:00
else
queue_delayed_work ( edac_workqueue , & edac_dev - > work ,
edac_dev - > delay ) ;
2007-07-19 12:49:36 +04:00
}
/*
2007-07-19 12:49:52 +04:00
* edac_device_workq_teardown
2007-07-19 12:49:36 +04:00
* stop the workq processing on this edac_dev
*/
2007-07-19 12:49:52 +04:00
void edac_device_workq_teardown ( struct edac_device_ctl_info * edac_dev )
2007-07-19 12:49:36 +04:00
{
int status ;
status = cancel_delayed_work ( & edac_dev - > work ) ;
if ( status = = 0 ) {
/* workq instance might be running, wait for it */
flush_workqueue ( edac_workqueue ) ;
}
}
/*
* edac_device_reset_delay_period
2007-07-19 12:50:30 +04:00
*
* need to stop any outstanding workq queued up at this time
* because we will be resetting the sleep time .
* Then restart the workq on the new delay
2007-07-19 12:49:36 +04:00
*/
2007-07-19 12:49:58 +04:00
void edac_device_reset_delay_period ( struct edac_device_ctl_info * edac_dev ,
2007-07-19 12:50:13 +04:00
unsigned long value )
2007-07-19 12:49:36 +04:00
{
2007-07-19 12:50:30 +04:00
/* cancel the current workq request, without the mutex lock */
2007-07-19 12:49:52 +04:00
edac_device_workq_teardown ( edac_dev ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:30 +04:00
/* acquire the mutex before doing the workq setup */
mutex_lock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
/* restart the workq request, with new delay value */
2007-07-19 12:49:52 +04:00
edac_device_workq_setup ( edac_dev , value ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
}
/**
* edac_device_add_device : Insert the ' edac_dev ' structure into the
* edac_device global list and create sysfs entries associated with
* edac_device structure .
* @ edac_device : pointer to the edac_device structure to be added to the list
* ' edac_device ' structure .
*
* Return :
* 0 Success
* ! 0 Failure
*/
2007-07-19 12:50:27 +04:00
int edac_device_add_device ( struct edac_device_ctl_info * edac_dev )
2007-07-19 12:49:36 +04:00
{
debugf0 ( " %s() \n " , __func__ ) ;
# ifdef CONFIG_EDAC_DEBUG
if ( edac_debug_level > = 3 )
edac_device_dump_device ( edac_dev ) ;
# endif
2007-07-19 12:50:22 +04:00
mutex_lock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
if ( add_edac_dev_to_global_list ( edac_dev ) )
goto fail0 ;
/* set load time so that error rate can be tracked */
edac_dev - > start_time = jiffies ;
/* create this instance's sysfs entries */
if ( edac_device_create_sysfs ( edac_dev ) ) {
edac_device_printk ( edac_dev , KERN_WARNING ,
2007-07-19 12:50:13 +04:00
" failed to create sysfs device \n " ) ;
2007-07-19 12:49:36 +04:00
goto fail1 ;
}
/* If there IS a check routine, then we are running POLLED */
if ( edac_dev - > edac_check ! = NULL ) {
/* This instance is NOW RUNNING */
edac_dev - > op_state = OP_RUNNING_POLL ;
2007-07-19 12:49:52 +04:00
/*
* enable workq processing on this instance ,
* default = 1000 msec
*/
edac_device_workq_setup ( edac_dev , 1000 ) ;
2007-07-19 12:49:36 +04:00
} else {
edac_dev - > op_state = OP_RUNNING_INTERRUPT ;
}
/* Report action taken */
edac_device_printk ( edac_dev , KERN_INFO ,
2007-07-19 12:50:13 +04:00
" Giving out device to module '%s' controller "
" '%s': DEV '%s' (%s) \n " ,
edac_dev - > mod_name ,
edac_dev - > ctl_name ,
dev_name ( edac_dev ) ,
2007-07-19 12:50:21 +04:00
edac_op_state_to_string ( edac_dev - > op_state ) ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
return 0 ;
2007-07-19 12:50:13 +04:00
fail1 :
2007-07-19 12:49:36 +04:00
/* Some error, so remove the entry from the lsit */
del_edac_device_from_global_list ( edac_dev ) ;
2007-07-19 12:50:13 +04:00
fail0 :
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
return 1 ;
}
EXPORT_SYMBOL_GPL ( edac_device_add_device ) ;
/**
* edac_device_del_device :
* Remove sysfs entries for specified edac_device structure and
* then remove edac_device structure from global list
*
* @ pdev :
* Pointer to ' struct device ' representing edac_device
* structure to remove .
*
* Return :
* Pointer to removed edac_device structure ,
* OR NULL if device not found .
*/
2007-07-19 12:49:58 +04:00
struct edac_device_ctl_info * edac_device_del_device ( struct device * dev )
2007-07-19 12:49:36 +04:00
{
struct edac_device_ctl_info * edac_dev ;
2007-07-19 12:50:33 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:22 +04:00
mutex_lock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:20 +04:00
/* Find the structure on the list, if not there, then leave */
edac_dev = find_edac_device_by_dev ( dev ) ;
if ( edac_dev = = NULL ) {
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
return NULL ;
}
/* mark this instance as OFFLINE */
edac_dev - > op_state = OP_OFFLINE ;
/* clear workq processing on this instance */
2007-07-19 12:49:52 +04:00
edac_device_workq_teardown ( edac_dev ) ;
2007-07-19 12:49:36 +04:00
/* deregister from global list */
del_edac_device_from_global_list ( edac_dev ) ;
2007-07-19 12:50:22 +04:00
mutex_unlock ( & device_ctls_mutex ) ;
2007-07-19 12:49:36 +04:00
2007-07-19 12:50:29 +04:00
/* Tear down the sysfs entries for this instance */
edac_device_remove_sysfs ( edac_dev ) ;
2007-07-19 12:49:36 +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 " ,
edac_dev - > dev_idx ,
edac_dev - > mod_name , edac_dev - > ctl_name , dev_name ( edac_dev ) ) ;
2007-07-19 12:49:36 +04:00
return edac_dev ;
}
2007-07-19 12:49:58 +04:00
EXPORT_SYMBOL_GPL ( edac_device_del_device ) ;
2007-07-19 12:49:36 +04:00
static inline int edac_device_get_log_ce ( struct edac_device_ctl_info * edac_dev )
{
return edac_dev - > log_ce ;
}
static inline int edac_device_get_log_ue ( struct edac_device_ctl_info * edac_dev )
{
return edac_dev - > log_ue ;
}
2007-07-19 12:49:58 +04:00
static inline int edac_device_get_panic_on_ue ( struct edac_device_ctl_info
2007-07-19 12:50:13 +04:00
* edac_dev )
2007-07-19 12:49:36 +04:00
{
return edac_dev - > panic_on_ue ;
}
/*
* edac_device_handle_ce
* perform a common output and handling of an ' edac_dev ' CE event
*/
void edac_device_handle_ce ( struct edac_device_ctl_info * edac_dev ,
2007-07-19 12:50:13 +04:00
int inst_nr , int block_nr , const char * msg )
2007-07-19 12:49:36 +04:00
{
struct edac_device_instance * instance ;
struct edac_device_block * block = NULL ;
if ( ( inst_nr > = edac_dev - > nr_instances ) | | ( inst_nr < 0 ) ) {
edac_device_printk ( edac_dev , KERN_ERR ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: 'instance' out of range "
" (%d >= %d) \n " , inst_nr ,
edac_dev - > nr_instances ) ;
2007-07-19 12:49:36 +04:00
return ;
}
instance = edac_dev - > instances + inst_nr ;
if ( ( block_nr > = instance - > nr_blocks ) | | ( block_nr < 0 ) ) {
edac_device_printk ( edac_dev , KERN_ERR ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: instance %d 'block' "
" out of range (%d >= %d) \n " ,
inst_nr , block_nr ,
instance - > nr_blocks ) ;
2007-07-19 12:49:36 +04:00
return ;
}
if ( instance - > nr_blocks > 0 ) {
block = instance - > blocks + block_nr ;
block - > counters . ce_count + + ;
}
/* Propogate the count up the 'totals' tree */
instance - > counters . ce_count + + ;
edac_dev - > counters . ce_count + + ;
if ( edac_device_get_log_ce ( edac_dev ) )
edac_device_printk ( edac_dev , KERN_WARNING ,
2007-07-19 12:50:13 +04:00
" CE: %s instance: %s block: %s '%s' \n " ,
edac_dev - > ctl_name , instance - > name ,
block ? block - > name : " N/A " , msg ) ;
2007-07-19 12:49:36 +04:00
}
EXPORT_SYMBOL_GPL ( edac_device_handle_ce ) ;
/*
* edac_device_handle_ue
* perform a common output and handling of an ' edac_dev ' UE event
*/
void edac_device_handle_ue ( struct edac_device_ctl_info * edac_dev ,
2007-07-19 12:50:13 +04:00
int inst_nr , int block_nr , const char * msg )
2007-07-19 12:49:36 +04:00
{
struct edac_device_instance * instance ;
struct edac_device_block * block = NULL ;
if ( ( inst_nr > = edac_dev - > nr_instances ) | | ( inst_nr < 0 ) ) {
edac_device_printk ( edac_dev , KERN_ERR ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: 'instance' out of range "
" (%d >= %d) \n " , inst_nr ,
edac_dev - > nr_instances ) ;
2007-07-19 12:49:36 +04:00
return ;
}
instance = edac_dev - > instances + inst_nr ;
if ( ( block_nr > = instance - > nr_blocks ) | | ( block_nr < 0 ) ) {
edac_device_printk ( edac_dev , KERN_ERR ,
2007-07-19 12:50:13 +04:00
" INTERNAL ERROR: instance %d 'block' "
" out of range (%d >= %d) \n " ,
inst_nr , block_nr ,
instance - > nr_blocks ) ;
2007-07-19 12:49:36 +04:00
return ;
}
if ( instance - > nr_blocks > 0 ) {
block = instance - > blocks + block_nr ;
block - > counters . ue_count + + ;
}
/* Propogate the count up the 'totals' tree */
instance - > counters . ue_count + + ;
edac_dev - > counters . ue_count + + ;
if ( edac_device_get_log_ue ( edac_dev ) )
edac_device_printk ( edac_dev , KERN_EMERG ,
2007-07-19 12:50:13 +04:00
" UE: %s instance: %s block: %s '%s' \n " ,
edac_dev - > ctl_name , instance - > name ,
block ? block - > name : " N/A " , msg ) ;
2007-07-19 12:49:36 +04:00
if ( edac_device_get_panic_on_ue ( edac_dev ) )
2007-07-19 12:50:11 +04:00
panic ( " EDAC %s: UE instance: %s block %s '%s' \n " ,
2007-07-19 12:50:13 +04:00
edac_dev - > ctl_name , instance - > name ,
block ? block - > name : " N/A " , msg ) ;
2007-07-19 12:49:36 +04:00
}
2007-07-19 12:49:58 +04:00
EXPORT_SYMBOL_GPL ( edac_device_handle_ue ) ;