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 .
*
*/
# 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>
# 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"
static DEFINE_MUTEX ( edac_pci_ctls_mutex ) ;
2008-04-29 12:03:17 +04:00
static LIST_HEAD ( edac_pci_list ) ;
2007-07-19 12:49:52 +04:00
/*
2007-07-26 21:41:15 +04:00
* edac_pci_alloc_ctl_info
*
* The alloc ( ) function for the ' edac_pci ' control info
* structure . The chip driver will allocate one of these for each
* edac_pci it is going to control / register with the EDAC CORE .
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 ;
void * pvt ;
unsigned int size ;
2007-07-26 21:41:15 +04:00
debugf1 ( " %s() \n " , __func__ ) ;
2007-07-19 12:49:52 +04:00
pci = ( struct edac_pci_ctl_info * ) 0 ;
pvt = edac_align_ptr ( & pci [ 1 ] , sz_pvt ) ;
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 ) ;
/*
* edac_pci_free_ctl_info ( )
2007-07-26 21:41:15 +04:00
*
* Last action on the pci control structure .
*
2008-02-03 18:12:34 +03:00
* call the remove sysfs information , which will unregister
2007-07-26 21:41:15 +04:00
* this control struct ' s kobj . When that kobj ' s ref count
* goes to zero , its release function will be call and then
* kfree ( ) the memory .
2007-07-19 12:49:52 +04:00
*/
void edac_pci_free_ctl_info ( struct edac_pci_ctl_info * pci )
{
2007-07-26 21:41:15 +04:00
debugf1 ( " %s() \n " , __func__ ) ;
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 ;
2007-07-26 21:41:15 +04:00
debugf1 ( " %s() \n " , __func__ ) ;
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 ;
2007-07-26 21:41:15 +04:00
debugf1 ( " %s() \n " , __func__ ) ;
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 " ,
2008-05-05 07:54:19 +04:00
rover - > dev - > bus_id , 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 ;
}
/*
* complete_edac_pci_list_del
2007-07-26 21:41:15 +04:00
*
* RCU completion callback to indicate item is deleted
2007-07-19 12:49:52 +04:00
*/
static void complete_edac_pci_list_del ( struct rcu_head * head )
{
struct edac_pci_ctl_info * pci ;
pci = container_of ( head , struct edac_pci_ctl_info , rcu ) ;
INIT_LIST_HEAD ( & pci - > link ) ;
complete ( & pci - > complete ) ;
}
/*
* 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 ) ;
init_completion ( & pci - > complete ) ;
call_rcu ( & pci - > rcu , complete_edac_pci_list_del ) ;
wait_for_completion ( & pci - > complete ) ;
}
2008-04-29 12:03:18 +04:00
#if 0
/* Older code, but might use in the future */
2007-07-19 12:49:52 +04:00
/*
* edac_pci_find ( )
* Search for an edac_pci_ctl_info structure whose index is ' idx '
*
* If found , return a pointer to the structure
* Else return NULL .
*
* Caller must hold pci_ctls_mutex .
*/
2007-07-19 12:49:58 +04:00
struct edac_pci_ctl_info * edac_pci_find ( int idx )
2007-07-19 12:49:52 +04:00
{
struct list_head * item ;
struct edac_pci_ctl_info * pci ;
/* Iterage over list, looking for exact match of ID */
list_for_each ( item , & edac_pci_list ) {
pci = list_entry ( item , struct edac_pci_ctl_info , link ) ;
if ( pci - > pci_idx > = idx ) {
if ( pci - > pci_idx = = idx )
return pci ;
2007-07-19 12:49:58 +04:00
/* not on list, so terminate early */
2007-07-19 12:49:52 +04:00
break ;
}
}
return NULL ;
}
EXPORT_SYMBOL_GPL ( edac_pci_find ) ;
2008-04-29 12:03:18 +04:00
# endif
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 )
{
struct delayed_work * d_work = ( struct delayed_work * ) work_req ;
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
2007-07-26 21:41:15 +04:00
debugf3 ( " %s() checking \n " , __func__ ) ;
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
if ( pci - > op_state = = OP_RUNNING_POLL ) {
/* we might be in POLL mode, but there may NOT be a poll func
*/
if ( ( pci - > edac_check ! = NULL ) & & 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 )
2008-02-07 11:14:51 +03:00
delay = round_jiffies_relative ( msecs_to_jiffies ( msec ) ) ;
2007-07-26 21:41:15 +04:00
else
delay = msecs_to_jiffies ( msec ) ;
/* Reschedule only if we are in POLL mode */
queue_delayed_work ( edac_workqueue , & pci - > work , delay ) ;
}
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
}
/*
* edac_pci_workq_setup ( )
* initialize a workq item for this edac_pci instance
* passing in the new delay period in msec
2007-07-26 21:41:15 +04:00
*
* locking model :
* called when ' edac_pci_ctls_mutex ' is locked
2007-07-19 12:49:52 +04:00
*/
static void edac_pci_workq_setup ( struct edac_pci_ctl_info * pci ,
2007-07-19 12:49:58 +04:00
unsigned int msec )
2007-07-19 12:49:52 +04:00
{
debugf0 ( " %s() \n " , __func__ ) ;
INIT_DELAYED_WORK ( & pci - > work , edac_pci_workq_function ) ;
2007-07-19 12:49:54 +04:00
queue_delayed_work ( edac_workqueue , & pci - > work ,
2007-07-19 12:50:13 +04:00
msecs_to_jiffies ( edac_pci_get_poll_msec ( ) ) ) ;
2007-07-19 12:49:52 +04:00
}
/*
* edac_pci_workq_teardown ( )
* stop the workq processing on this edac_pci instance
*/
static void edac_pci_workq_teardown ( struct edac_pci_ctl_info * pci )
{
int status ;
2007-07-26 21:41:15 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:49:52 +04:00
status = cancel_delayed_work ( & pci - > work ) ;
if ( status = = 0 )
flush_workqueue ( edac_workqueue ) ;
}
/*
* edac_pci_reset_delay_period
2007-07-26 21:41:15 +04:00
*
* called with a new period value for the workq period
* a ) stop current workq timer
* b ) restart workq timer with new value
2007-07-19 12:49:52 +04:00
*/
void edac_pci_reset_delay_period ( struct edac_pci_ctl_info * pci ,
2007-07-19 12:49:58 +04:00
unsigned long value )
2007-07-19 12:49:52 +04:00
{
2007-07-26 21:41:15 +04:00
debugf0 ( " %s() \n " , __func__ ) ;
2007-07-19 12:49:52 +04:00
edac_pci_workq_teardown ( pci ) ;
2007-07-26 21:41:15 +04:00
/* need to lock for the setup */
mutex_lock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
edac_pci_workq_setup ( pci , value ) ;
2007-07-26 21:41:15 +04:00
mutex_unlock ( & edac_pci_ctls_mutex ) ;
2007-07-19 12:49:52 +04:00
}
EXPORT_SYMBOL_GPL ( edac_pci_reset_delay_period ) ;
/*
* edac_pci_add_device : Insert the ' edac_dev ' structure into the
* edac_pci global list and create sysfs entries associated with
* edac_pci structure .
* @ pci : pointer to the edac_device structure to be added to the list
* @ edac_idx : A unique numeric identifier to be assigned to the
* ' edac_pci ' structure .
*
* Return :
* 0 Success
* ! 0 Failure
*/
int edac_pci_add_device ( struct edac_pci_ctl_info * pci , int edac_idx )
{
debugf0 ( " %s() \n " , __func__ ) ;
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 ;
}
if ( pci - > edac_check ! = NULL ) {
pci - > op_state = OP_RUNNING_POLL ;
edac_pci_workq_setup ( pci , 1000 ) ;
} else {
pci - > op_state = OP_RUNNING_INTERRUPT ;
}
edac_pci_printk ( pci , KERN_INFO ,
2007-07-19 12:49:58 +04:00
" Giving out device to module '%s' controller '%s': "
" DEV '%s' (%s) \n " ,
pci - > mod_name ,
pci - > ctl_name ,
2008-05-05 07:54:19 +04:00
edac_dev_name ( pci ) , 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 ) ;
/*
* edac_pci_del_device ( )
* Remove sysfs entries for specified edac_pci structure and
* then remove edac_pci structure from global list
*
* @ dev :
* Pointer to ' struct device ' representing edac_pci structure
* to remove
*
* Return :
* Pointer to removed edac_pci structure ,
* or NULL if device not found
*/
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 ;
debugf0 ( " %s() \n " , __func__ ) ;
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 ) ;
/* stop the workq timer */
edac_pci_workq_teardown ( pci ) ;
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
{
2007-07-26 21:41:15 +04:00
debugf4 ( " %s() \n " , __func__ ) ;
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-26 21:41:15 +04:00
/*
* edac_pci_create_generic_ctl
*
* A generic constructor for a PCI parity polling device
* Some systems have more than one domain of PCI busses .
* For systems with one domain , then this API will
* provide for a generic poller .
*
* This routine calls the edac_pci_alloc_ctl_info ( ) for
* the generic device , with default values
*/
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 ;
pci - > edac_check = edac_pci_generic_check ;
pdata - > edac_idx = edac_pci_idx + + ;
if ( edac_pci_add_device ( pci , pdata - > edac_idx ) > 0 ) {
debugf3 ( " %s(): failed edac_pci_add_device() \n " , __func__ ) ;
edac_pci_free_ctl_info ( pci ) ;
return NULL ;
}
return pci ;
}
EXPORT_SYMBOL_GPL ( edac_pci_create_generic_ctl ) ;
2007-07-26 21:41:15 +04:00
/*
* edac_pci_release_generic_ctl
*
* The release function of a generic EDAC PCI polling device
*/
2007-07-19 12:49:52 +04:00
void edac_pci_release_generic_ctl ( struct edac_pci_ctl_info * pci )
{
2007-07-26 21:41:15 +04:00
debugf0 ( " %s() pci mod=%s \n " , __func__ , pci - > mod_name ) ;
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 ) ;