summaryrefslogtreecommitdiffstats
path: root/drivers/edac/edac_pci.c
diff options
context:
space:
mode:
authorDoug Thompson <dougthompson@xmission.com>2007-07-26 10:41:15 -0700
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-07-26 11:35:18 -0700
commitd4c1465b7de9686c4c5aa533b15c09ab014aab3a (patch)
tree695434e881a3b395db782fe82e95eee2631b8a2e /drivers/edac/edac_pci.c
parentbce19683c17485b584b62b984d6dcf5332181588 (diff)
drivers/edac: fix edac_pci sysfs
This patch fixes sysfs exit code for the EDAC PCI device in a similiar manner and the previous fixes for EDAC_MC and EDAC_DEVICE. It removes the old (and incorrect) completion model and uses reference counts on per instance kobjects and on the edac core module. This pattern was applied to the edac_mc and edac_device code, but the EDAC PCI code was missed. In addition, this fixes a system hang after a low level driver was unloaded. (A cleanup function was called twice, which really screwed things up) Cc: Greg KH <greg@kroah.com> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Signed-off-by: Doug Thompson <dougthompson@xmission.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/edac/edac_pci.c')
-rw-r--r--drivers/edac/edac_pci.c162
1 files changed, 111 insertions, 51 deletions
diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c
index d9cd5e048ce..5dee9f50414 100644
--- a/drivers/edac/edac_pci.c
+++ b/drivers/edac/edac_pci.c
@@ -31,20 +31,12 @@
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.
+ * 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.
*/
struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt,
const char *edac_pci_name)
@@ -53,47 +45,59 @@ struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt,
void *pvt;
unsigned int size;
+ debugf1("%s()\n", __func__);
+
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)
+ /* Alloc the needed control struct memory */
+ pci = kzalloc(size, GFP_KERNEL);
+ if (pci == NULL)
return NULL;
+ /* Now much private space */
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
+ *
+ * Last action on the pci control structure.
+ *
+ * call the remove sysfs informaton, which will unregister
+ * 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.
*/
void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci)
{
- kfree(pci);
-}
+ debugf1("%s()\n", __func__);
+ edac_pci_remove_sysfs(pci);
+}
EXPORT_SYMBOL_GPL(edac_pci_free_ctl_info);
/*
* find_edac_pci_by_dev()
* scans the edac_pci list for a specific 'struct device *'
+ *
+ * return NULL if not found, or return control struct pointer
*/
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__);
+ debugf1("%s()\n", __func__);
list_for_each(item, &edac_pci_list) {
pci = list_entry(item, struct edac_pci_ctl_info, link);
@@ -118,10 +122,13 @@ 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;
+ debugf1("%s()\n", __func__);
+
insert_before = &edac_pci_list;
/* Determine if already on the list */
- if (unlikely((rover = find_edac_pci_by_dev(pci->dev)) != NULL))
+ rover = find_edac_pci_by_dev(pci->dev);
+ if (unlikely(rover != NULL))
goto fail0;
/* Insert in ascending order by 'pci_idx', so find position */
@@ -157,6 +164,8 @@ fail1:
/*
* complete_edac_pci_list_del
+ *
+ * RCU completion callback to indicate item is deleted
*/
static void complete_edac_pci_list_del(struct rcu_head *head)
{
@@ -169,6 +178,8 @@ static void complete_edac_pci_list_del(struct rcu_head *head)
/*
* del_edac_pci_from_global_list
+ *
+ * remove the PCI control struct from the global list
*/
static void del_edac_pci_from_global_list(struct edac_pci_ctl_info *pci)
{
@@ -207,35 +218,52 @@ struct edac_pci_ctl_info *edac_pci_find(int idx)
return NULL;
}
-
EXPORT_SYMBOL_GPL(edac_pci_find);
/*
* edac_pci_workq_function()
- * performs the operation scheduled by a workq request
+ *
+ * periodic function that performs the operation
+ * scheduled by a workq request, for a given PCI control struct
*/
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);
+ int msec;
+ unsigned long delay;
- edac_lock_pci_list();
+ debugf3("%s() checking\n", __func__);
- if ((pci->op_state == OP_RUNNING_POLL) &&
- (pci->edac_check != NULL) && (edac_pci_get_check_errors()))
- pci->edac_check(pci);
+ mutex_lock(&edac_pci_ctls_mutex);
- edac_unlock_pci_list();
+ 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)
+ delay = round_jiffies(msecs_to_jiffies(msec));
+ else
+ delay = msecs_to_jiffies(msec);
+
+ /* Reschedule only if we are in POLL mode */
+ queue_delayed_work(edac_workqueue, &pci->work, delay);
+ }
- /* Reschedule */
- queue_delayed_work(edac_workqueue, &pci->work,
- msecs_to_jiffies(edac_pci_get_poll_msec()));
+ mutex_unlock(&edac_pci_ctls_mutex);
}
/*
* edac_pci_workq_setup()
* initialize a workq item for this edac_pci instance
* passing in the new delay period in msec
+ *
+ * locking model:
+ * called when 'edac_pci_ctls_mutex' is locked
*/
static void edac_pci_workq_setup(struct edac_pci_ctl_info *pci,
unsigned int msec)
@@ -255,6 +283,8 @@ static void edac_pci_workq_teardown(struct edac_pci_ctl_info *pci)
{
int status;
+ debugf0("%s()\n", __func__);
+
status = cancel_delayed_work(&pci->work);
if (status == 0)
flush_workqueue(edac_workqueue);
@@ -262,19 +292,25 @@ static void edac_pci_workq_teardown(struct edac_pci_ctl_info *pci)
/*
* edac_pci_reset_delay_period
+ *
+ * called with a new period value for the workq period
+ * a) stop current workq timer
+ * b) restart workq timer with new value
*/
void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci,
unsigned long value)
{
- edac_lock_pci_list();
+ debugf0("%s()\n", __func__);
edac_pci_workq_teardown(pci);
+ /* need to lock for the setup */
+ mutex_lock(&edac_pci_ctls_mutex);
+
edac_pci_workq_setup(pci, value);
- edac_unlock_pci_list();
+ mutex_unlock(&edac_pci_ctls_mutex);
}
-
EXPORT_SYMBOL_GPL(edac_pci_reset_delay_period);
/*
@@ -294,14 +330,13 @@ int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx)
debugf0("%s()\n", __func__);
pci->pci_idx = edac_idx;
+ pci->start_time = jiffies;
- edac_lock_pci_list();
+ mutex_lock(&edac_pci_ctls_mutex);
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");
@@ -323,16 +358,16 @@ int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx)
pci->ctl_name,
dev_name(pci), edac_op_state_to_string(pci->op_state));
- edac_unlock_pci_list();
+ mutex_unlock(&edac_pci_ctls_mutex);
return 0;
+ /* error unwind stack */
fail1:
del_edac_pci_from_global_list(pci);
fail0:
- edac_unlock_pci_list();
+ mutex_unlock(&edac_pci_ctls_mutex);
return 1;
}
-
EXPORT_SYMBOL_GPL(edac_pci_add_device);
/*
@@ -354,22 +389,25 @@ struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev)
debugf0("%s()\n", __func__);
- edac_lock_pci_list();
+ mutex_lock(&edac_pci_ctls_mutex);
- if ((pci = find_edac_pci_by_dev(dev)) == NULL) {
- edac_unlock_pci_list();
+ /* 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);
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();
+ mutex_unlock(&edac_pci_ctls_mutex);
+
+ /* stop the workq timer */
+ edac_pci_workq_teardown(pci);
edac_printk(KERN_INFO, EDAC_PCI,
"Removed device %d for %s %s: DEV %s\n",
@@ -377,14 +415,20 @@ struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev)
return pci;
}
-
EXPORT_SYMBOL_GPL(edac_pci_del_device);
+/*
+ * edac_pci_generic_check
+ *
+ * a Generic parity check API
+ */
void edac_pci_generic_check(struct edac_pci_ctl_info *pci)
{
+ debugf4("%s()\n", __func__);
edac_pci_do_parity_check();
}
+/* free running instance index counter */
static int edac_pci_idx;
#define EDAC_PCI_GENCTL_NAME "EDAC PCI controller"
@@ -392,6 +436,17 @@ struct edac_pci_gen_data {
int edac_idx;
};
+/*
+ * 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
+ */
struct edac_pci_ctl_info *edac_pci_create_generic_ctl(struct device *dev,
const char *mod_name)
{
@@ -421,13 +476,18 @@ struct edac_pci_ctl_info *edac_pci_create_generic_ctl(struct device *dev,
return pci;
}
-
EXPORT_SYMBOL_GPL(edac_pci_create_generic_ctl);
+/*
+ * edac_pci_release_generic_ctl
+ *
+ * The release function of a generic EDAC PCI polling device
+ */
void edac_pci_release_generic_ctl(struct edac_pci_ctl_info *pci)
{
+ debugf0("%s() pci mod=%s\n", __func__, pci->mod_name);
+
edac_pci_del_device(pci->dev);
edac_pci_free_ctl_info(pci);
}
-
EXPORT_SYMBOL_GPL(edac_pci_release_generic_ctl);