2007-07-19 12:49:52 +04:00
/*
* EDAC PCI component
*
* Author : Dave Jiang < djiang @ mvista . com >
*
* 2007 ( c ) MontaVista Software , Inc . This file is licensed under
* the terms of the GNU General Public License version 2. This program
* is licensed " as is " without any warranty of any kind , whether express
* or implied .
*
*/
2016-10-29 14:56:00 +03:00
# include <asm/page.h>
2016-12-24 22:46:01 +03:00
# include <linux/uaccess.h>
2016-10-29 14:56:00 +03:00
# include <linux/ctype.h>
# include <linux/highmem.h>
# include <linux/init.h>
2007-07-19 12:49:52 +04:00
# include <linux/module.h>
2016-10-29 14:56:00 +03:00
# include <linux/slab.h>
2007-07-19 12:49:52 +04:00
# include <linux/smp.h>
2016-10-29 14:56:00 +03:00
# include <linux/spinlock.h>
2007-07-19 12:49:52 +04:00
# include <linux/sysctl.h>
# include <linux/timer.h>
2016-10-29 14:56:00 +03:00
# include "edac_pci.h"
2007-07-19 12:49:52 +04:00
# include "edac_module.h"
static DEFINE_MUTEX ( edac_pci_ctls_mutex ) ;
2008-04-29 12:03:17 +04:00
static LIST_HEAD ( edac_pci_list ) ;
2009-04-03 03:58:47 +04:00
static atomic_t pci_indexes = ATOMIC_INIT ( 0 ) ;
2007-07-19 12:49:52 +04:00
2007-07-19 12:49:58 +04:00
struct edac_pci_ctl_info * edac_pci_alloc_ctl_info ( unsigned int sz_pvt ,
2007-07-19 12:50:13 +04:00
const char * edac_pci_name )
2007-07-19 12:49:52 +04:00
{
struct edac_pci_ctl_info * pci ;
2012-04-16 17:18:12 +04:00
void * p = NULL , * pvt ;
2007-07-19 12:49:52 +04:00
unsigned int size ;
2012-04-30 00:08:39 +04:00
edac_dbg ( 1 , " \n " ) ;
2007-07-26 21:41:15 +04:00
2012-04-16 17:18:12 +04:00
pci = edac_align_ptr ( & p , sizeof ( * pci ) , 1 ) ;
pvt = edac_align_ptr ( & p , 1 , sz_pvt ) ;
2007-07-19 12:49:52 +04:00
size = ( ( unsigned long ) pvt ) + sz_pvt ;
2007-07-26 21:41:15 +04:00
/* Alloc the needed control struct memory */
pci = kzalloc ( size , GFP_KERNEL ) ;
if ( pci = = NULL )
2007-07-19 12:49:52 +04:00
return NULL ;
2007-07-26 21:41:15 +04:00
/* Now much private space */
2007-07-19 12:49:52 +04:00
pvt = sz_pvt ? ( ( char * ) pci ) + ( ( unsigned long ) pvt ) : NULL ;
pci - > pvt_info = pvt ;
pci - > op_state = OP_ALLOC ;
2007-07-19 12:49:58 +04:00
snprintf ( pci - > name , strlen ( edac_pci_name ) + 1 , " %s " , edac_pci_name ) ;
2007-07-19 12:49:52 +04:00
return pci ;
}
EXPORT_SYMBOL_GPL ( edac_pci_alloc_ctl_info ) ;
void edac_pci_free_ctl_info ( struct edac_pci_ctl_info * pci )
{
2012-04-30 00:08:39 +04:00
edac_dbg ( 1 , " \n " ) ;
2007-07-19 12:49:58 +04:00
2007-07-26 21:41:15 +04:00
edac_pci_remove_sysfs ( pci ) ;
}
2007-07-19 12:49:52 +04:00
EXPORT_SYMBOL_GPL ( edac_pci_free_ctl_info ) ;
/*
* find_edac_pci_by_dev ( )
* scans the edac_pci list for a specific ' struct device * '
2007-07-26 21:41:15 +04:00
*
* return NULL if not found , or return control struct pointer
2007-07-19 12:49:52 +04:00
*/
2007-07-19 12:49:58 +04:00
static struct edac_pci_ctl_info * find_edac_pci_by_dev ( struct device * dev )
2007-07-19 12:49:52 +04:00
{
struct edac_pci_ctl_info * pci ;
struct list_head * item ;
2012-04-30 00:08:39 +04:00
edac_dbg ( 1 , " \n " ) ;
2007-07-19 12:49:52 +04:00
list_for_each ( item , & edac_pci_list ) {
pci = list_entry ( item , struct edac_pci_ctl_info , link ) ;
if ( pci - > dev = = dev )
return pci ;
}
return NULL ;
}
/*
* add_edac_pci_to_global_list
* Before calling this function , caller must assign a unique value to
* edac_dev - > pci_idx .
* Return :
* 0 on success
* 1 on failure
*/
static int add_edac_pci_to_global_list ( struct edac_pci_ctl_info * pci )
{
struct list_head * item , * insert_before ;
struct edac_pci_ctl_info * rover ;
2012-04-30 00:08:39 +04:00
edac_dbg ( 1 , " \n " ) ;
2007-07-26 21:41:15 +04:00
2007-07-19 12:49:52 +04:00
insert_before = & edac_pci_list ;
/* Determine if already on the list */
2007-07-26 21:41:15 +04:00
rover = find_edac_pci_by_dev ( pci - > dev ) ;
if ( unlikely ( rover ! = NULL ) )
2007-07-19 12:49:52 +04:00
goto fail0 ;
/* Insert in ascending order by 'pci_idx', so find position */
list_for_each ( item , & edac_pci_list ) {
rover = list_entry ( item , struct edac_pci_ctl_info , link ) ;
if ( rover - > pci_idx > = pci - > pci_idx ) {
if ( unlikely ( rover - > pci_idx = = pci - > pci_idx ) )
goto fail1 ;
insert_before = item ;
break ;
}
}
list_add_tail_rcu ( & pci - > link , insert_before ) ;
return 0 ;
2007-07-19 12:50:13 +04:00
fail0 :
2007-07-19 12:49:52 +04:00
edac_printk ( KERN_WARNING , EDAC_PCI ,
2007-07-19 12:50:13 +04:00
" %s (%s) %s %s already assigned %d \n " ,
2009-01-07 01:42:57 +03:00
dev_name ( rover - > dev ) , edac_dev_name ( rover ) ,
2007-07-19 12:50:13 +04:00
rover - > mod_name , rover - > ctl_name , rover - > pci_idx ) ;
2007-07-19 12:49:52 +04:00
return 1 ;
2007-07-19 12:50:13 +04:00
fail1 :
2007-07-19 12:49:52 +04:00
edac_printk ( KERN_WARNING , EDAC_PCI ,
2007-07-19 12:50:13 +04:00
" but in low-level driver: attempt to assign \n "
" \t duplicate pci_idx %d in %s() \n " , rover - > pci_idx ,
__func__ ) ;
2007-07-19 12:49:52 +04:00
return 1 ;
}
/*
* del_edac_pci_from_global_list
2007-07-26 21:41:15 +04:00
*
* remove the PCI control struct from the global list
2007-07-19 12:49:52 +04:00
*/
static void del_edac_pci_from_global_list ( struct edac_pci_ctl_info * pci )
{
list_del_rcu ( & pci - > link ) ;
2011-05-27 03:25:58 +04:00
/* these are for safe removal of devices from global list while
* NMI handlers may be traversing list
*/
synchronize_rcu ( ) ;
INIT_LIST_HEAD ( & pci - > link ) ;
2007-07-19 12:49:52 +04:00
}
/*
* edac_pci_workq_function ( )
2007-07-26 21:41:15 +04:00
*
* periodic function that performs the operation
* scheduled by a workq request , for a given PCI control struct
2007-07-19 12:49:52 +04:00
*/
static void edac_pci_workq_function ( struct work_struct * work_req )
{
2009-04-14 01:40:21 +04:00
struct delayed_work * d_work = to_delayed_work ( work_req ) ;
2007-07-19 12:49:52 +04:00
struct edac_pci_ctl_info * pci = to_edac_pci_ctl_work ( d_work ) ;
2007-07-26 21:41:15 +04:00
int msec ;
unsigned long delay ;
2007-07-19 12:49:52 +04:00
2012-04-30 00:08:39 +04:00
edac_dbg ( 3 , " checking \n " ) ;
2007-07-19 12:49:52 +04:00
2007-07-26 21:41:15 +04:00
mutex_lock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
2016-02-02 13:36:11 +03:00
if ( pci - > op_state ! = OP_RUNNING_POLL ) {
mutex_unlock ( & edac_pci_ctls_mutex ) ;
return ;
2007-07-26 21:41:15 +04:00
}
2007-07-19 12:49:52 +04:00
2016-02-02 13:36:11 +03:00
if ( edac_pci_get_check_errors ( ) )
pci - > edac_check ( pci ) ;
/* if we are on a one second period, then use round */
msec = edac_pci_get_poll_msec ( ) ;
if ( msec = = 1000 )
delay = round_jiffies_relative ( msecs_to_jiffies ( msec ) ) ;
else
delay = msecs_to_jiffies ( msec ) ;
edac_queue_work ( & pci - > work , delay ) ;
2007-07-26 21:41:15 +04:00
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
}
2009-04-03 03:58:47 +04:00
int edac_pci_alloc_index ( void )
{
return atomic_inc_return ( & pci_indexes ) - 1 ;
}
EXPORT_SYMBOL_GPL ( edac_pci_alloc_index ) ;
2007-07-19 12:49:52 +04:00
int edac_pci_add_device ( struct edac_pci_ctl_info * pci , int edac_idx )
{
2012-04-30 00:08:39 +04:00
edac_dbg ( 0 , " \n " ) ;
2007-07-19 12:49:52 +04:00
pci - > pci_idx = edac_idx ;
2007-07-26 21:41:15 +04:00
pci - > start_time = jiffies ;
2007-07-19 12:49:52 +04:00
2007-07-26 21:41:15 +04:00
mutex_lock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
if ( add_edac_pci_to_global_list ( pci ) )
goto fail0 ;
if ( edac_pci_create_sysfs ( pci ) ) {
edac_pci_printk ( pci , KERN_WARNING ,
" failed to create sysfs pci \n " ) ;
goto fail1 ;
}
2016-02-02 12:59:53 +03:00
if ( pci - > edac_check ) {
2007-07-19 12:49:52 +04:00
pci - > op_state = OP_RUNNING_POLL ;
2016-02-02 13:06:41 +03:00
INIT_DELAYED_WORK ( & pci - > work , edac_pci_workq_function ) ;
edac_queue_work ( & pci - > work , msecs_to_jiffies ( edac_pci_get_poll_msec ( ) ) ) ;
2007-07-19 12:49:52 +04:00
} else {
pci - > op_state = OP_RUNNING_INTERRUPT ;
}
edac_pci_printk ( pci , KERN_INFO ,
2013-10-10 20:22:36 +04:00
" Giving out device to module %s controller %s: DEV %s (%s) \n " ,
pci - > mod_name , pci - > ctl_name , pci - > dev_name ,
edac_op_state_to_string ( pci - > op_state ) ) ;
2007-07-19 12:49:52 +04:00
2007-07-26 21:41:15 +04:00
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
return 0 ;
2007-07-26 21:41:15 +04:00
/* error unwind stack */
2007-07-19 12:50:13 +04:00
fail1 :
2007-07-19 12:49:52 +04:00
del_edac_pci_from_global_list ( pci ) ;
2007-07-19 12:50:13 +04:00
fail0 :
2007-07-26 21:41:15 +04:00
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
return 1 ;
}
EXPORT_SYMBOL_GPL ( edac_pci_add_device ) ;
2007-07-19 12:49:58 +04:00
struct edac_pci_ctl_info * edac_pci_del_device ( struct device * dev )
2007-07-19 12:49:52 +04:00
{
struct edac_pci_ctl_info * pci ;
2012-04-30 00:08:39 +04:00
edac_dbg ( 0 , " \n " ) ;
2007-07-19 12:49:52 +04:00
2007-07-26 21:41:15 +04:00
mutex_lock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
2007-07-26 21:41:15 +04:00
/* ensure the control struct is on the global list
* if not , then leave
*/
pci = find_edac_pci_by_dev ( dev ) ;
if ( pci = = NULL ) {
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
return NULL ;
}
pci - > op_state = OP_OFFLINE ;
del_edac_pci_from_global_list ( pci ) ;
2007-07-26 21:41:15 +04:00
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2016-02-02 12:59:53 +03:00
if ( pci - > edac_check )
2016-02-02 13:06:41 +03:00
edac_stop_work ( & pci - > work ) ;
2007-07-19 12:49:52 +04:00
edac_printk ( KERN_INFO , EDAC_PCI ,
2007-07-19 12:50:13 +04:00
" Removed device %d for %s %s: DEV %s \n " ,
2008-05-05 07:54:19 +04:00
pci - > pci_idx , pci - > mod_name , pci - > ctl_name , edac_dev_name ( pci ) ) ;
2007-07-19 12:49:52 +04:00
return pci ;
}
EXPORT_SYMBOL_GPL ( edac_pci_del_device ) ;
2007-07-26 21:41:15 +04:00
/*
* edac_pci_generic_check
*
* a Generic parity check API
*/
2008-04-29 12:03:18 +04:00
static void edac_pci_generic_check ( struct edac_pci_ctl_info * pci )
2007-07-19 12:49:52 +04:00
{
2012-04-30 00:08:39 +04:00
edac_dbg ( 4 , " \n " ) ;
2007-07-19 12:49:52 +04:00
edac_pci_do_parity_check ( ) ;
}
2007-07-26 21:41:15 +04:00
/* free running instance index counter */
2007-07-19 12:50:19 +04:00
static int edac_pci_idx ;
2007-07-19 12:49:52 +04:00
# define EDAC_PCI_GENCTL_NAME "EDAC PCI controller"
struct edac_pci_gen_data {
int edac_idx ;
} ;
2007-07-19 12:49:58 +04:00
struct edac_pci_ctl_info * edac_pci_create_generic_ctl ( struct device * dev ,
2007-07-19 12:50:13 +04:00
const char * mod_name )
2007-07-19 12:49:52 +04:00
{
struct edac_pci_ctl_info * pci ;
struct edac_pci_gen_data * pdata ;
pci = edac_pci_alloc_ctl_info ( sizeof ( * pdata ) , EDAC_PCI_GENCTL_NAME ) ;
if ( ! pci )
return NULL ;
pdata = pci - > pvt_info ;
pci - > dev = dev ;
dev_set_drvdata ( pci - > dev , pci ) ;
pci - > dev_name = pci_name ( to_pci_dev ( dev ) ) ;
pci - > mod_name = mod_name ;
pci - > ctl_name = EDAC_PCI_GENCTL_NAME ;
2012-09-10 17:52:27 +04:00
if ( edac_op_state = = EDAC_OPSTATE_POLL )
pci - > edac_check = edac_pci_generic_check ;
2007-07-19 12:49:52 +04:00
pdata - > edac_idx = edac_pci_idx + + ;
if ( edac_pci_add_device ( pci , pdata - > edac_idx ) > 0 ) {
2012-04-30 00:08:39 +04:00
edac_dbg ( 3 , " failed edac_pci_add_device() \n " ) ;
2007-07-19 12:49:52 +04:00
edac_pci_free_ctl_info ( pci ) ;
return NULL ;
}
return pci ;
}
EXPORT_SYMBOL_GPL ( edac_pci_create_generic_ctl ) ;
void edac_pci_release_generic_ctl ( struct edac_pci_ctl_info * pci )
{
2012-04-30 00:08:39 +04:00
edac_dbg ( 0 , " pci mod=%s \n " , pci - > mod_name ) ;
2007-07-26 21:41:15 +04:00
2007-07-19 12:49:52 +04:00
edac_pci_del_device ( pci - > dev ) ;
edac_pci_free_ctl_info ( pci ) ;
}
EXPORT_SYMBOL_GPL ( edac_pci_release_generic_ctl ) ;