Commit 91b99041 authored by Dave Jiang's avatar Dave Jiang Committed by Linus Torvalds

drivers/edac: updated PCI monitoring

Moving PCI to a per-instance device model

This should include the correct sysfs setup as well. Please review.
Signed-off-by: default avatarDave Jiang <djiang@mvista.com>
Signed-off-by: default avatarDouglas Thompson <dougthompson@xmission.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 81d87cb1
...@@ -11,9 +11,12 @@ obj-$(CONFIG_EDAC) := edac_stub.o ...@@ -11,9 +11,12 @@ obj-$(CONFIG_EDAC) := edac_stub.o
obj-$(CONFIG_EDAC_MM_EDAC) += edac_core.o obj-$(CONFIG_EDAC_MM_EDAC) += edac_core.o
edac_core-objs := edac_mc.o edac_device.o edac_mc_sysfs.o edac_pci_sysfs.o edac_core-objs := edac_mc.o edac_device.o edac_mc_sysfs.o edac_pci_sysfs.o
edac_core-objs += edac_module.o edac_device_sysfs.o edac_core-objs += edac_module.o edac_device_sysfs.o
ifdef CONFIG_PCI
edac_core-objs += edac_pci.o edac_pci_sysfs.o
endif
obj-$(CONFIG_EDAC_AMD76X) += amd76x_edac.o obj-$(CONFIG_EDAC_AMD76X) += amd76x_edac.o
obj-$(CONFIG_EDAC_I5000) += i5000_edac.o obj-$(CONFIG_EDAC_I5000) += i5000_edac.o
obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o
......
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
static int force_function_unhide; static int force_function_unhide;
static struct edac_pci_ctl_info *e752x_pci;
#define e752x_printk(level, fmt, arg...) \ #define e752x_printk(level, fmt, arg...) \
edac_printk(level, "e752x", fmt, ##arg) edac_printk(level, "e752x", fmt, ##arg)
...@@ -1040,6 +1042,17 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx) ...@@ -1040,6 +1042,17 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx)
e752x_init_error_reporting_regs(pvt); e752x_init_error_reporting_regs(pvt);
e752x_get_error_info(mci, &discard); /* clear other MCH errors */ e752x_get_error_info(mci, &discard); /* clear other MCH errors */
/* allocating generic PCI control info */
e752x_pci = edac_pci_create_generic_ctl(&pdev->dev, EDAC_MOD_STR);
if (!e752x_pci) {
printk(KERN_WARNING
"%s(): Unable to create PCI control\n",
__func__);
printk(KERN_WARNING
"%s(): PCI error report via EDAC not setup\n",
__func__);
}
/* get this far and it's successful */ /* get this far and it's successful */
debugf3("%s(): success\n", __func__); debugf3("%s(): success\n", __func__);
return 0; return 0;
...@@ -1073,6 +1086,9 @@ static void __devexit e752x_remove_one(struct pci_dev *pdev) ...@@ -1073,6 +1086,9 @@ static void __devexit e752x_remove_one(struct pci_dev *pdev)
debugf0("%s()\n", __func__); debugf0("%s()\n", __func__);
if (e752x_pci)
edac_pci_release_generic_ctl(e752x_pci);
if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL) if ((mci = edac_mc_del_mc(&pdev->dev)) == NULL)
return; return;
......
...@@ -60,6 +60,10 @@ ...@@ -60,6 +60,10 @@
#define edac_device_printk(ctl, level, fmt, arg...) \ #define edac_device_printk(ctl, level, fmt, arg...) \
printk(level "EDAC DEVICE%d: " fmt, ctl->dev_idx, ##arg) printk(level "EDAC DEVICE%d: " fmt, ctl->dev_idx, ##arg)
/* edac_pci printk */
#define edac_pci_printk(ctl, level, fmt, arg...) \
printk(level "EDAC PCI%d: " fmt, ctl->pci_idx, ##arg)
/* prefixes for edac_printk() and edac_mc_printk() */ /* prefixes for edac_printk() and edac_mc_printk() */
#define EDAC_MC "MC" #define EDAC_MC "MC"
#define EDAC_PCI "PCI" #define EDAC_PCI "PCI"
...@@ -200,6 +204,13 @@ enum scrub_type { ...@@ -200,6 +204,13 @@ enum scrub_type {
/* FIXME - should have notify capabilities: NMI, LOG, PROC, etc */ /* FIXME - should have notify capabilities: NMI, LOG, PROC, etc */
/* EDAC internal operation states */
#define OP_ALLOC 0x100
#define OP_RUNNING_POLL 0x201
#define OP_RUNNING_INTERRUPT 0x202
#define OP_RUNNING_POLL_INTR 0x203
#define OP_OFFLINE 0x300
extern char * edac_align_ptr(void *ptr, unsigned size); extern char * edac_align_ptr(void *ptr, unsigned size);
/* /*
...@@ -520,12 +531,6 @@ struct edac_device_ctl_info { ...@@ -520,12 +531,6 @@ struct edac_device_ctl_info {
/* the internal state of this controller instance */ /* the internal state of this controller instance */
int op_state; int op_state;
#define OP_ALLOC 0x100
#define OP_RUNNING_POLL 0x201
#define OP_RUNNING_INTERRUPT 0x202
#define OP_RUNNING_POLL_INTR 0x203
#define OP_OFFLINE 0x300
/* work struct for this instance */ /* work struct for this instance */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)) #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
struct delayed_work work; struct delayed_work work;
...@@ -626,6 +631,84 @@ extern void edac_device_free_ctl_info( struct edac_device_ctl_info *ctl_info); ...@@ -626,6 +631,84 @@ extern void edac_device_free_ctl_info( struct edac_device_ctl_info *ctl_info);
#ifdef CONFIG_PCI #ifdef CONFIG_PCI
struct edac_pci_counter {
atomic_t pe_count;
atomic_t npe_count;
};
/*
* Abstract edac_pci control info structure
*
*/
struct edac_pci_ctl_info {
/* for global list of edac_pci_ctl_info structs */
struct list_head link;
int pci_idx;
/* Per instance controls for this edac_device */
int check_parity_error; /* boolean for checking parity errs */
int log_parity_error; /* boolean for logging parity errs */
int panic_on_pe; /* boolean for panic'ing on a PE */
unsigned poll_msec; /* number of milliseconds to poll interval */
unsigned long delay; /* number of jiffies for poll_msec */
struct sysdev_class *edac_class; /* pointer to class */
/* the internal state of this controller instance */
int op_state;
/* work struct for this instance */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
struct delayed_work work;
#else
struct work_struct work;
#endif
/* pointer to edac polling checking routine:
* If NOT NULL: points to polling check routine
* If NULL: Then assumes INTERRUPT operation, where
* MC driver will receive events
*/
void (*edac_check) (struct edac_pci_ctl_info * edac_dev);
struct device *dev; /* pointer to device structure */
const char *mod_name; /* module name */
const char *ctl_name; /* edac controller name */
const char *dev_name; /* pci/platform/etc... name */
void *pvt_info; /* pointer to 'private driver' info */
unsigned long start_time;/* edac_pci load start time (jiffies)*/
/* these are for safe removal of devices from global list while
* NMI handlers may be traversing list
*/
struct rcu_head rcu;
struct completion complete;
/* sysfs top name under 'edac' directory
* and instance name:
* cpu/cpu0/...
* cpu/cpu1/...
* cpu/cpu2/...
* ...
*/
char name[EDAC_DEVICE_NAME_LEN + 1];
/* Event counters for the this whole EDAC Device */
struct edac_pci_counter counters;
/* edac sysfs device control for the 'name'
* device this structure controls
*/
struct kobject kobj;
struct completion kobj_complete;
};
#define to_edac_pci_ctl_work(w) \
container_of(w, struct edac_pci_ctl_info,work)
/* write all or some bits in a byte-register*/ /* write all or some bits in a byte-register*/
static inline void pci_write_bits8(struct pci_dev *pdev, int offset, u8 value, static inline void pci_write_bits8(struct pci_dev *pdev, int offset, u8 value,
u8 mask) u8 mask)
...@@ -726,5 +809,30 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev, ...@@ -726,5 +809,30 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev,
extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev, extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev,
int inst_nr, int block_nr, const char *msg); int inst_nr, int block_nr, const char *msg);
/*
* edac_pci APIs
*/
extern struct edac_pci_ctl_info *
edac_pci_alloc_ctl_info(unsigned int sz_pvt, const char *edac_pci_name);
extern void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci);
extern void
edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci, unsigned long value);
extern int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx);
extern struct edac_pci_ctl_info * edac_pci_del_device(struct device *dev);
extern struct edac_pci_ctl_info *
edac_pci_create_generic_ctl(struct device *dev, const char *mod_name);
extern void edac_pci_release_generic_ctl(struct edac_pci_ctl_info *pci);
extern int edac_pci_create_sysfs(struct edac_pci_ctl_info *pci);
extern void edac_pci_remove_sysfs(struct edac_pci_ctl_info *pci);
/*
* edac misc APIs
*/
extern char * edac_op_state_toString(int op_state);
#endif /* _EDAC_CORE_H_ */ #endif /* _EDAC_CORE_H_ */
...@@ -418,27 +418,6 @@ void edac_device_reset_delay_period( ...@@ -418,27 +418,6 @@ void edac_device_reset_delay_period(
unlock_device_list(); unlock_device_list();
} }
/*
* edac_op_state_toString(edac_dev)
*/
static char *edac_op_state_toString(struct edac_device_ctl_info *edac_dev)
{
int opstate = edac_dev->op_state;
if (opstate == OP_RUNNING_POLL)
return "POLLED";
else if (opstate == OP_RUNNING_INTERRUPT)
return "INTERRUPT";
else if (opstate == OP_RUNNING_POLL_INTR)
return "POLL-INTR";
else if (opstate == OP_ALLOC)
return "ALLOC";
else if (opstate == OP_OFFLINE)
return "OFFLINE";
return "UNKNOWN";
}
/** /**
* edac_device_add_device: Insert the 'edac_dev' structure into the * edac_device_add_device: Insert the 'edac_dev' structure into the
* edac_device global list and create sysfs entries associated with * edac_device global list and create sysfs entries associated with
...@@ -496,7 +475,7 @@ int edac_device_add_device(struct edac_device_ctl_info *edac_dev, int edac_idx) ...@@ -496,7 +475,7 @@ int edac_device_add_device(struct edac_device_ctl_info *edac_dev, int edac_idx)
edac_dev->mod_name, edac_dev->mod_name,
edac_dev->ctl_name, edac_dev->ctl_name,
dev_name(edac_dev), dev_name(edac_dev),
edac_op_state_toString(edac_dev) edac_op_state_toString(edac_dev->op_state)
); );
unlock_device_list(); unlock_device_list();
......
...@@ -34,6 +34,25 @@ static struct sysdev_class edac_class = { ...@@ -34,6 +34,25 @@ static struct sysdev_class edac_class = {
}; };
static int edac_class_valid = 0; static int edac_class_valid = 0;
/*
* edac_op_state_toString()
*/
char * edac_op_state_toString(int opstate)
{
if (opstate == OP_RUNNING_POLL)
return "POLLED";
else if (opstate == OP_RUNNING_INTERRUPT)
return "INTERRUPT";
else if (opstate == OP_RUNNING_POLL_INTR)
return "POLL-INTR";
else if (opstate == OP_ALLOC)
return "ALLOC";
else if (opstate == OP_OFFLINE)
return "OFFLINE";
return "UNKNOWN";
}
/* /*
* edac_get_edac_class() * edac_get_edac_class()
* *
...@@ -153,26 +172,16 @@ static int __init edac_init(void) ...@@ -153,26 +172,16 @@ static int __init edac_init(void)
goto error_sysfs; goto error_sysfs;
} }
/* Create the PCI parity sysfs entries */
if (edac_sysfs_pci_setup()) {
edac_printk(KERN_ERR, EDAC_MC,
"PCI: Error initializing sysfs code\n");
err = -ENODEV;
goto error_mem;
}
/* Setup/Initialize the edac_device system */ /* Setup/Initialize the edac_device system */
err = edac_workqueue_setup(); err = edac_workqueue_setup();
if (err) { if (err) {
edac_printk(KERN_ERR, EDAC_MC, "init WorkQueue failure\n"); edac_printk(KERN_ERR, EDAC_MC, "init WorkQueue failure\n");
goto error_pci; goto error_mem;
} }
return 0; return 0;
/* Error teardown stack */ /* Error teardown stack */
error_pci:
edac_sysfs_pci_teardown();
error_mem: error_mem:
edac_sysfs_memctrl_teardown(); edac_sysfs_memctrl_teardown();
error_sysfs: error_sysfs:
...@@ -192,7 +201,6 @@ static void __exit edac_exit(void) ...@@ -192,7 +201,6 @@ static void __exit edac_exit(void)
/* tear down the various subsystems*/ /* tear down the various subsystems*/
edac_workqueue_teardown(); edac_workqueue_teardown();
edac_sysfs_memctrl_teardown(); edac_sysfs_memctrl_teardown();
edac_sysfs_pci_teardown();
edac_unregister_sysfs_edac_name(); edac_unregister_sysfs_edac_name();
} }
......
/*
* 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);
static struct list_head edac_pci_list = LIST_HEAD_INIT(edac_pci_list);
static inline void edac_lock_pci_list(void)
{
mutex_lock(&edac_pci_ctls_mutex);
}
static inline void edac_unlock_pci_list(void)
{
mutex_unlock(&edac_pci_ctls_mutex);
}
/*
* The alloc() and free() functions 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.
*/
struct edac_pci_ctl_info * edac_pci_alloc_ctl_info(
unsigned int sz_pvt,
const char *edac_pci_name)
{
struct edac_pci_ctl_info *pci;
void *pvt;
unsigned int size;
pci = (struct edac_pci_ctl_info *)0;
pvt = edac_align_ptr(&pci[1], sz_pvt);
size = ((unsigned long)pvt) + sz_pvt;
if ((pci = kzalloc(size, GFP_KERNEL)) == NULL)
return NULL;
pvt = sz_pvt ? ((char *)pci) + ((unsigned long)pvt) : NULL;
pci->pvt_info = pvt;
pci->op_state = OP_ALLOC;
snprintf(pci->name, strlen(edac_pci_name)+1, "%s", edac_pci_name);
return pci;
}
EXPORT_SYMBOL_GPL(edac_pci_alloc_ctl_info);
/*
* edac_pci_free_ctl_info()
* frees the memory allocated by edac_pci_alloc_ctl_info() function
*/
void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci)
{
kfree(pci);
}
EXPORT_SYMBOL_GPL(edac_pci_free_ctl_info);
/*
* find_edac_pci_by_dev()
* scans the edac_pci list for a specific 'struct device *'
*/
static struct edac_pci_ctl_info * find_edac_pci_by_dev(struct device *dev)
{
struct edac_pci_ctl_info *pci;
struct list_head *item;
debugf3("%s()\n", __func__);
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;
insert_before = &edac_pci_list;
/* Determine if already on the list */
if (unlikely((rover = find_edac_pci_by_dev(pci->dev)) != NULL))
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;
fail0:
edac_printk(KERN_WARNING, EDAC_PCI,
"%s (%s) %s %s already assigned %d\n",
rover->dev->bus_id, dev_name(rover),
rover->mod_name, rover->ctl_name, rover->pci_idx);
return 1;
fail1:
edac_printk(KERN_WARNING, EDAC_PCI,
"but in low-level driver: attempt to assign\n"
"\tduplicate pci_idx %d in %s()\n", rover->pci_idx, __func__);
return 1;
}
/*
* complete_edac_pci_list_del
*/
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
*/
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);
}
/*
* 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.
*/
struct edac_pci_ctl_info * edac_pci_find(int idx)
{
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;
/* not on list, so terminate early */
break;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(edac_pci_find);
/*
* edac_pci_workq_function()
* performs the operation scheduled by a workq request
*/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
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);
#else
static void edac_pci_workq_function(void *ptr)
{
struct edac_pci_ctl_info *pci = ptr;
#endif
edac_lock_pci_list();
if ((pci->op_state == OP_RUNNING_POLL) &&
(pci->edac_check != NULL) &&
(pci->check_parity_error))
pci->edac_check(pci);
edac_unlock_pci_list();
/* Reschedule */
queue_delayed_work(edac_workqueue, &pci->work, pci->delay);
}
/*
* edac_pci_workq_setup()
* initialize a workq item for this edac_pci instance
* passing in the new delay period in msec
*/
static void edac_pci_workq_setup(struct edac_pci_ctl_info *pci,
unsigned int msec)
{
debugf0("%s()\n", __func__);
pci->poll_msec = msec;
edac_calc_delay(pci);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20))
INIT_DELAYED_WORK(&pci->work, edac_pci_workq_function);
#else
INIT_WORK(&pci->work, edac_pci_workq_function, pci);
#endif
queue_delayed_work(edac_workqueue, &pci->work, pci->delay);
}
/*
* 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;
status = cancel_delayed_work(&pci->work);
if (status == 0)
flush_workqueue(edac_workqueue);
}
/*
* edac_pci_reset_delay_period
*/
void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci,
unsigned long value)
{
edac_lock_pci_list();
edac_pci_workq_teardown(pci);
edac_pci_workq_setup(pci, value);
edac_unlock_pci_list();
}
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;
edac_lock_pci_list();
if (add_edac_pci_to_global_list(pci))
goto fail0;
pci->start_time = jiffies;
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,
"Giving out device to module '%s' controller '%s':"
" DEV '%s' (%s)\n",
pci->mod_name,
pci->ctl_name,
dev_name(pci),
edac_op_state_toString(pci->op_state));
edac_unlock_pci_list();
return 0;
fail1:
del_edac_pci_from_global_list(pci);
fail0:
edac_unlock_pci_list();
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
*/
struct edac_pci_ctl_info * edac_pci_del_device(struct device *dev)
{
struct edac_pci_ctl_info *pci;
debugf0("%s()\n", __func__);
edac_lock_pci_list();
if ((pci = find_edac_pci_by_dev(dev)) == NULL) {
edac_unlock_pci_list();
return NULL;
}
pci->op_state = OP_OFFLINE;
edac_pci_workq_teardown(pci);
edac_pci_remove_sysfs(pci);
del_edac_pci_from_global_list(pci);
edac_unlock_pci_list();
edac_printk(KERN_INFO, EDAC_PCI,
"Removed device %d for %s %s: DEV %s\n",
pci->pci_idx,
pci->mod_name,
pci->ctl_name,
dev_name(pci));
return pci;
}
EXPORT_SYMBOL_GPL(edac_pci_del_device);
static inline int edac_pci_get_log_pe(struct edac_pci_ctl_info *pci)
{
return pci->log_parity_error;
}
static inline int edac_pci_get_panic_on_pe(struct edac_pci_ctl_info *pci)
{
return pci->panic_on_pe;
}
void edac_pci_generic_check(struct edac_pci_ctl_info *pci)
{
edac_pci_do_parity_check();
}
static int edac_pci_idx = 0;
#define EDAC_PCI_GENCTL_NAME "EDAC PCI controller"
struct edac_pci_gen_data {
int edac_idx;
};
struct edac_pci_ctl_info *
edac_pci_create_generic_ctl(struct device *dev, const char *mod_name)
{
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);
void edac_pci_release_generic_ctl(struct edac_pci_ctl_info *pci)
{
edac_pci_del_device(pci->dev);
edac_pci_free_ctl_info(pci);
}
EXPORT_SYMBOL_GPL(edac_pci_release_generic_ctl);
...@@ -15,13 +15,142 @@ ...@@ -15,13 +15,142 @@
#ifdef CONFIG_PCI #ifdef CONFIG_PCI
static int check_pci_parity = 0; /* default YES check PCI parity */
static int panic_on_pci_parity; /* default no panic on PCI Parity */ #define EDAC_PCI_SYMLINK "device"
static int check_pci_errors = 0; /* default YES check PCI parity */
static int panic_on_pci_parity = 0; /* default no panic on PCI Parity */
static int log_pci_errs = 1;
static atomic_t pci_parity_count = ATOMIC_INIT(0); static atomic_t pci_parity_count = ATOMIC_INIT(0);
static atomic_t pci_nonparity_count = ATOMIC_INIT(0);
static struct kobject edac_pci_kobj; /* /sys/devices/system/edac/pci */ static struct kobject edac_pci_kobj; /* /sys/devices/system/edac/pci */
static struct completion edac_pci_kobj_complete; static struct completion edac_pci_kobj_complete;
static atomic_t edac_pci_sysfs_refcount = ATOMIC_INIT(0);
/**************************** EDAC PCI sysfs instance *******************/
static ssize_t instance_pe_count_show(struct edac_pci_ctl_info *pci, char *data)
{
return sprintf(data,"%u\n", atomic_read(&pci->counters.pe_count));
}
static ssize_t instance_npe_count_show(struct edac_pci_ctl_info *pci,
char *data)
{
return sprintf(data,"%u\n", atomic_read(&pci->counters.npe_count));
}
#define to_instance(k) container_of(k, struct edac_pci_ctl_info, kobj)
#define to_instance_attr(a) container_of(a, struct instance_attribute, attr)
/* DEVICE instance kobject release() function */
static void edac_pci_instance_release(struct kobject *kobj)
{
struct edac_pci_ctl_info *pci;
debugf1("%s()\n", __func__);
pci = to_instance(kobj);
complete(&pci->kobj_complete);
}
/* instance specific attribute structure */
struct instance_attribute {
struct attribute attr;
ssize_t (*show)(struct edac_pci_ctl_info *, char *);
ssize_t (*store)(struct edac_pci_ctl_info *, const char *, size_t);
};
/* Function to 'show' fields from the edac_pci 'instance' structure */
static ssize_t edac_pci_instance_show(struct kobject *kobj,
struct attribute *attr,
char *buffer)
{
struct edac_pci_ctl_info *pci = to_instance(kobj);
struct instance_attribute *instance_attr = to_instance_attr(attr);
if (instance_attr->show)
return instance_attr->show(pci, buffer);
return -EIO;
}
/* Function to 'store' fields into the edac_pci 'instance' structure */
static ssize_t edac_pci_instance_store(struct kobject *kobj,
struct attribute *attr,
const char *buffer, size_t count)
{
struct edac_pci_ctl_info *pci = to_instance(kobj);
struct instance_attribute *instance_attr = to_instance_attr(attr);
if (instance_attr->store)
return instance_attr->store(pci, buffer, count);
return -EIO;
}
static struct sysfs_ops pci_instance_ops = {
.show = edac_pci_instance_show,
.store = edac_pci_instance_store
};
#define INSTANCE_ATTR(_name, _mode, _show, _store) \
static struct instance_attribute attr_instance_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
};
INSTANCE_ATTR(pe_count, S_IRUGO, instance_pe_count_show, NULL);
INSTANCE_ATTR(npe_count, S_IRUGO, instance_npe_count_show, NULL);
/* pci instance attributes */
static struct instance_attribute *pci_instance_attr[] = {
&attr_instance_pe_count,
&attr_instance_npe_count,
NULL
};
/* the ktype for pci instance */
static struct kobj_type ktype_pci_instance = {
.release = edac_pci_instance_release,
.sysfs_ops = &pci_instance_ops,
.default_attrs = (struct attribute **)pci_instance_attr,
};
static int edac_pci_create_instance_kobj(struct edac_pci_ctl_info *pci, int idx)
{
int err;
pci->kobj.parent = &edac_pci_kobj;
pci->kobj.ktype = &ktype_pci_instance;
err = kobject_set_name(&pci->kobj, "pci%d", idx);
if (err)
return err;
err = kobject_register(&pci->kobj);
if (err != 0) {
debugf2("%s() failed to register instance pci%d\n",
__func__, idx);
return err;
}
debugf1("%s() Register instance 'pci%d' kobject\n", __func__, idx);
return 0;
}
static void
edac_pci_delete_instance_kobj(struct edac_pci_ctl_info *pci, int idx)
{
init_completion(&pci->kobj_complete);
kobject_unregister(&pci->kobj);
wait_for_completion(&pci->kobj_complete);
}
/***************************** EDAC PCI sysfs root **********************/
#define to_edacpci(k) container_of(k, struct edac_pci_ctl_info, kobj)
#define to_edacpci_attr(a) container_of(a, struct edac_pci_attr, attr)
static ssize_t edac_pci_int_show(void *ptr, char *buffer) static ssize_t edac_pci_int_show(void *ptr, char *buffer)
{ {
...@@ -91,25 +220,34 @@ static struct edac_pci_dev_attribute edac_pci_attr_##_name = { \ ...@@ -91,25 +220,34 @@ static struct edac_pci_dev_attribute edac_pci_attr_##_name = { \
}; };
/* PCI Parity control files */ /* PCI Parity control files */
EDAC_PCI_ATTR(check_pci_parity, S_IRUGO|S_IWUSR, edac_pci_int_show, EDAC_PCI_ATTR(check_pci_errors, S_IRUGO|S_IWUSR, edac_pci_int_show,
edac_pci_int_store);
EDAC_PCI_ATTR(log_pci_errs, S_IRUGO|S_IWUSR, edac_pci_int_show,
edac_pci_int_store); edac_pci_int_store);
EDAC_PCI_ATTR(panic_on_pci_parity, S_IRUGO|S_IWUSR, edac_pci_int_show, EDAC_PCI_ATTR(panic_on_pci_parity, S_IRUGO|S_IWUSR, edac_pci_int_show,
edac_pci_int_store); edac_pci_int_store);
EDAC_PCI_ATTR(pci_parity_count, S_IRUGO, edac_pci_int_show, NULL); EDAC_PCI_ATTR(pci_parity_count, S_IRUGO, edac_pci_int_show, NULL);
EDAC_PCI_ATTR(pci_nonparity_count, S_IRUGO, edac_pci_int_show, NULL);
/* Base Attributes of the memory ECC object */ /* Base Attributes of the memory ECC object */
static struct edac_pci_dev_attribute *edac_pci_attr[] = { static struct edac_pci_dev_attribute *edac_pci_attr[] = {
&edac_pci_attr_check_pci_parity, &edac_pci_attr_check_pci_errors,
&edac_pci_attr_log_pci_errs,
&edac_pci_attr_panic_on_pci_parity, &edac_pci_attr_panic_on_pci_parity,
&edac_pci_attr_pci_parity_count, &edac_pci_attr_pci_parity_count,
&edac_pci_attr_pci_nonparity_count,
NULL, NULL,
}; };
/* No memory to release */ /* No memory to release */
static void edac_pci_release(struct kobject *kobj) static void edac_pci_release(struct kobject *kobj)
{ {
struct edac_pci_ctl_info *pci;
pci = to_edacpci(kobj);
debugf1("%s()\n", __func__); debugf1("%s()\n", __func__);
complete(&edac_pci_kobj_complete); complete(&pci->kobj_complete);
} }
static struct kobj_type ktype_edac_pci = { static struct kobj_type ktype_edac_pci = {
...@@ -124,7 +262,7 @@ static struct kobj_type ktype_edac_pci = { ...@@ -124,7 +262,7 @@ static struct kobj_type ktype_edac_pci = {
* setup the sysfs for EDAC PCI attributes * setup the sysfs for EDAC PCI attributes
* assumes edac_class has already been initialized * assumes edac_class has already been initialized
*/ */
int edac_sysfs_pci_setup(void) int edac_pci_register_main_kobj(void)
{ {
int err; int err;
struct sysdev_class *edac_class; struct sysdev_class *edac_class;
...@@ -132,32 +270,39 @@ int edac_sysfs_pci_setup(void) ...@@ -132,32 +270,39 @@ int edac_sysfs_pci_setup(void)
debugf1("%s()\n", __func__); debugf1("%s()\n", __func__);
edac_class = edac_get_edac_class(); edac_class = edac_get_edac_class();
if (edac_class == NULL) {
debugf1("%s() no edac_class\n", __func__);
return -ENODEV;
}
memset(&edac_pci_kobj, 0, sizeof(edac_pci_kobj));
edac_pci_kobj.parent = &edac_class->kset.kobj;
edac_pci_kobj.ktype = &ktype_edac_pci; edac_pci_kobj.ktype = &ktype_edac_pci;
edac_pci_kobj.parent = &edac_class->kset.kobj;
err = kobject_set_name(&edac_pci_kobj, "pci"); err = kobject_set_name(&edac_pci_kobj, "pci");
if(err)
return err;
if (!err) { /* Instanstiate the pci object */
/* Instanstiate the pci object */ /* FIXME: maybe new sysdev_create_subdir() */
/* FIXME: maybe new sysdev_create_subdir() */ err = kobject_register(&edac_pci_kobj);
err = kobject_register(&edac_pci_kobj);
if (err) if (err) {
debugf1("Failed to register '.../edac/pci'\n"); debugf1("Failed to register '.../edac/pci'\n");
else return err;
debugf1("Registered '.../edac/pci' kobject\n");
} }
return err; debugf1("Registered '.../edac/pci' kobject\n");
return 0;
} }
/* /*
* edac_sysfs_pci_teardown * edac_pci_unregister_main_kobj()
* *
* perform the sysfs teardown for the PCI attributes * perform the sysfs teardown for the PCI attributes
*/ */
void edac_sysfs_pci_teardown(void) void edac_pci_unregister_main_kobj(void)
{ {
debugf0("%s()\n", __func__); debugf0("%s()\n", __func__);
init_completion(&edac_pci_kobj_complete); init_completion(&edac_pci_kobj_complete);
...@@ -165,7 +310,53 @@ void edac_sysfs_pci_teardown(void) ...@@ -165,7 +310,53 @@ void edac_sysfs_pci_teardown(void)
wait_for_completion(&edac_pci_kobj_complete); wait_for_completion(&edac_pci_kobj_complete);
} }
int edac_pci_create_sysfs(struct edac_pci_ctl_info *pci)
{
int err;
struct kobject *edac_kobj = &pci->kobj;
if (atomic_inc_return(&edac_pci_sysfs_refcount) == 1) {
err = edac_pci_register_main_kobj();
if (err) {
atomic_dec(&edac_pci_sysfs_refcount);
return err;
}
}
err = edac_pci_create_instance_kobj(pci, pci->pci_idx);
if (err) {
if (atomic_dec_return(&edac_pci_sysfs_refcount) == 0)
edac_pci_unregister_main_kobj();
}
debugf0("%s() idx=%d\n", __func__, pci->pci_idx);
err = sysfs_create_link(edac_kobj,
&pci->dev->kobj,
EDAC_PCI_SYMLINK);
if (err) {
debugf0("%s() sysfs_create_link() returned err= %d\n",
__func__, err);
return err;
}
return 0;
}
void edac_pci_remove_sysfs(struct edac_pci_ctl_info *pci)
{
debugf0("%s()\n", __func__);
edac_pci_delete_instance_kobj(pci, pci->pci_idx);
sysfs_remove_link(&pci->kobj, EDAC_PCI_SYMLINK);
if (atomic_dec_return(&edac_pci_sysfs_refcount) == 0)
edac_pci_unregister_main_kobj();
}
/************************ PCI error handling *************************/
static u16 get_pci_parity_status(struct pci_dev *dev, int secondary) static u16 get_pci_parity_status(struct pci_dev *dev, int secondary)
{ {
int where; int where;
...@@ -231,10 +422,12 @@ static void edac_pci_dev_parity_test(struct pci_dev *dev) ...@@ -231,10 +422,12 @@ static void edac_pci_dev_parity_test(struct pci_dev *dev)
/* check the status reg for errors */ /* check the status reg for errors */
if (status) { if (status) {
if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) {
edac_printk(KERN_CRIT, EDAC_PCI, edac_printk(KERN_CRIT, EDAC_PCI,
"Signaled System Error on %s\n", "Signaled System Error on %s\n",
pci_name(dev)); pci_name(dev));
atomic_inc(&pci_nonparity_count);
}
if (status & (PCI_STATUS_PARITY)) { if (status & (PCI_STATUS_PARITY)) {
edac_printk(KERN_CRIT, EDAC_PCI, edac_printk(KERN_CRIT, EDAC_PCI,
...@@ -267,10 +460,12 @@ static void edac_pci_dev_parity_test(struct pci_dev *dev) ...@@ -267,10 +460,12 @@ static void edac_pci_dev_parity_test(struct pci_dev *dev)
/* check the secondary status reg for errors */ /* check the secondary status reg for errors */
if (status) { if (status) {
if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) if (status & (PCI_STATUS_SIG_SYSTEM_ERROR)) {
edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " edac_printk(KERN_CRIT, EDAC_PCI, "Bridge "
"Signaled System Error on %s\n", "Signaled System Error on %s\n",
pci_name(dev)); pci_name(dev));
atomic_inc(&pci_nonparity_count);
}
if (status & (PCI_STATUS_PARITY)) { if (status & (PCI_STATUS_PARITY)) {
edac_printk(KERN_CRIT, EDAC_PCI, "Bridge " edac_printk(KERN_CRIT, EDAC_PCI, "Bridge "
...@@ -321,7 +516,7 @@ void edac_pci_do_parity_check(void) ...@@ -321,7 +516,7 @@ void edac_pci_do_parity_check(void)
debugf3("%s()\n", __func__); debugf3("%s()\n", __func__);
if (!check_pci_parity) if (!check_pci_errors)
return; return;
before_count = atomic_read(&pci_parity_count); before_count = atomic_read(&pci_parity_count);
...@@ -348,13 +543,49 @@ void edac_pci_clear_parity_errors(void) ...@@ -348,13 +543,49 @@ void edac_pci_clear_parity_errors(void)
*/ */
edac_pci_dev_parity_iterator(edac_pci_dev_parity_clear); edac_pci_dev_parity_iterator(edac_pci_dev_parity_clear);
} }
void edac_pci_handle_pe(struct edac_pci_ctl_info *pci, const char *msg)
{
/* global PE counter incremented by edac_pci_do_parity_check() */
atomic_inc(&pci->counters.pe_count);
if (log_pci_errs)
edac_pci_printk(pci, KERN_WARNING,
"Parity Error ctl: %s %d: %s\n",
pci->ctl_name, pci->pci_idx, msg);
/*
* poke all PCI devices and see which one is the troublemaker
* panic() is called if set
*/
edac_pci_do_parity_check();
}
EXPORT_SYMBOL_GPL(edac_pci_handle_pe);
void edac_pci_handle_npe(struct edac_pci_ctl_info *pci, const char *msg)
{
/* global NPE counter incremented by edac_pci_do_parity_check() */
atomic_inc(&pci->counters.npe_count);
if (log_pci_errs)
edac_pci_printk(pci, KERN_WARNING,
"Non-Parity Error ctl: %s %d: %s\n",
pci->ctl_name, pci->pci_idx, msg);
/*
* poke all PCI devices and see which one is the troublemaker
* panic() is called if set
*/
edac_pci_do_parity_check();
}
EXPORT_SYMBOL_GPL(edac_pci_handle_npe);
/* /*
* Define the PCI parameter to the module * Define the PCI parameter to the module
*/ */
module_param(check_pci_parity, int, 0644); module_param(check_pci_errors, int, 0644);
MODULE_PARM_DESC(check_pci_parity, "Check for PCI bus parity errors: 0=off 1=on"); MODULE_PARM_DESC(check_pci_errors, "Check for PCI bus parity errors: 0=off 1=on");
module_param(panic_on_pci_parity, int, 0644); module_param(panic_on_pci_parity, int, 0644);
MODULE_PARM_DESC(panic_on_pci_parity, "Panic on PCI Bus Parity error: 0=off 1=on"); MODULE_PARM_DESC(panic_on_pci_parity, "Panic on PCI Bus Parity error: 0=off 1=on");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment