xref: /linux/drivers/edac/edac_device.c (revision 621cde16e49b3ecf7d59a8106a20aaebfb4a59a9)
1e27e3dacSDouglas Thompson 
2e27e3dacSDouglas Thompson /*
3e27e3dacSDouglas Thompson  * edac_device.c
4e27e3dacSDouglas Thompson  * (C) 2007 www.douglaskthompson.com
5e27e3dacSDouglas Thompson  *
6e27e3dacSDouglas Thompson  * This file may be distributed under the terms of the
7e27e3dacSDouglas Thompson  * GNU General Public License.
8e27e3dacSDouglas Thompson  *
9e27e3dacSDouglas Thompson  * Written by Doug Thompson <norsk5@xmission.com>
10e27e3dacSDouglas Thompson  *
11e27e3dacSDouglas Thompson  * edac_device API implementation
12e27e3dacSDouglas Thompson  * 19 Jan 2007
13e27e3dacSDouglas Thompson  */
14e27e3dacSDouglas Thompson 
15e27e3dacSDouglas Thompson #include <asm/page.h>
167c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
176d8ef247SMauro Carvalho Chehab #include <linux/ctype.h>
186d8ef247SMauro Carvalho Chehab #include <linux/highmem.h>
196d8ef247SMauro Carvalho Chehab #include <linux/init.h>
206d8ef247SMauro Carvalho Chehab #include <linux/jiffies.h>
216d8ef247SMauro Carvalho Chehab #include <linux/module.h>
226d8ef247SMauro Carvalho Chehab #include <linux/slab.h>
236d8ef247SMauro Carvalho Chehab #include <linux/smp.h>
246d8ef247SMauro Carvalho Chehab #include <linux/spinlock.h>
256d8ef247SMauro Carvalho Chehab #include <linux/sysctl.h>
266d8ef247SMauro Carvalho Chehab #include <linux/timer.h>
27e27e3dacSDouglas Thompson 
286d8ef247SMauro Carvalho Chehab #include "edac_device.h"
29e27e3dacSDouglas Thompson #include "edac_module.h"
30e27e3dacSDouglas Thompson 
31bf52fa4aSDoug Thompson /* lock for the list: 'edac_device_list', manipulation of this list
32bf52fa4aSDoug Thompson  * is protected by the 'device_ctls_mutex' lock
33bf52fa4aSDoug Thompson  */
340ca84761SDoug Thompson static DEFINE_MUTEX(device_ctls_mutex);
35ff6ac2a6SRobert P. J. Day static LIST_HEAD(edac_device_list);
36e27e3dacSDouglas Thompson 
37cec669ffSManivannan Sadhasivam /* Default workqueue processing interval on this instance, in msecs */
38cec669ffSManivannan Sadhasivam #define DEFAULT_POLL_INTERVAL 1000
39cec669ffSManivannan Sadhasivam 
40e27e3dacSDouglas Thompson #ifdef CONFIG_EDAC_DEBUG
edac_device_dump_device(struct edac_device_ctl_info * edac_dev)41e27e3dacSDouglas Thompson static void edac_device_dump_device(struct edac_device_ctl_info *edac_dev)
42e27e3dacSDouglas Thompson {
43956b9ba1SJoe Perches 	edac_dbg(3, "\tedac_dev = %p dev_idx=%d\n",
44956b9ba1SJoe Perches 		 edac_dev, edac_dev->dev_idx);
45956b9ba1SJoe Perches 	edac_dbg(4, "\tedac_dev->edac_check = %p\n", edac_dev->edac_check);
46956b9ba1SJoe Perches 	edac_dbg(3, "\tdev = %p\n", edac_dev->dev);
47956b9ba1SJoe Perches 	edac_dbg(3, "\tmod_name:ctl_name = %s:%s\n",
48e27e3dacSDouglas Thompson 		 edac_dev->mod_name, edac_dev->ctl_name);
49956b9ba1SJoe Perches 	edac_dbg(3, "\tpvt_info = %p\n\n", edac_dev->pvt_info);
50e27e3dacSDouglas Thompson }
51e27e3dacSDouglas Thompson #endif				/* CONFIG_EDAC_DEBUG */
52e27e3dacSDouglas Thompson 
530d24a49eSBorislav Petkov /*
540d24a49eSBorislav Petkov  * @off_val: zero, 1, or other based offset
550d24a49eSBorislav Petkov  */
560d24a49eSBorislav Petkov struct edac_device_ctl_info *
edac_device_alloc_ctl_info(unsigned pvt_sz,char * dev_name,unsigned nr_instances,char * blk_name,unsigned nr_blocks,unsigned off_val,int device_index)570d24a49eSBorislav Petkov edac_device_alloc_ctl_info(unsigned pvt_sz, char *dev_name, unsigned nr_instances,
580d24a49eSBorislav Petkov 			   char *blk_name, unsigned nr_blocks, unsigned off_val,
59*48bc8869SJiri Slaby (SUSE) 			   int device_index)
60e27e3dacSDouglas Thompson {
610d24a49eSBorislav Petkov 	struct edac_device_block *dev_blk, *blk_p, *blk;
620d24a49eSBorislav Petkov 	struct edac_device_instance *dev_inst, *inst;
630d24a49eSBorislav Petkov 	struct edac_device_ctl_info *dev_ctl;
64*48bc8869SJiri Slaby (SUSE) 	unsigned instance, block;
659fb9ce39SBorislav Petkov 	void *pvt;
661c3631ffSDouglas Thompson 	int err;
67e27e3dacSDouglas Thompson 
68956b9ba1SJoe Perches 	edac_dbg(4, "instances=%d blocks=%d\n", nr_instances, nr_blocks);
69e27e3dacSDouglas Thompson 
709fb9ce39SBorislav Petkov 	dev_ctl = kzalloc(sizeof(struct edac_device_ctl_info), GFP_KERNEL);
719fb9ce39SBorislav Petkov 	if (!dev_ctl)
72e27e3dacSDouglas Thompson 		return NULL;
73e27e3dacSDouglas Thompson 
7413088b65SBorislav Petkov 	dev_inst = kcalloc(nr_instances, sizeof(struct edac_device_instance), GFP_KERNEL);
759fb9ce39SBorislav Petkov 	if (!dev_inst)
769fb9ce39SBorislav Petkov 		goto free;
77e27e3dacSDouglas Thompson 
789fb9ce39SBorislav Petkov 	dev_ctl->instances = dev_inst;
799fb9ce39SBorislav Petkov 
8013088b65SBorislav Petkov 	dev_blk = kcalloc(nr_instances * nr_blocks, sizeof(struct edac_device_block), GFP_KERNEL);
819fb9ce39SBorislav Petkov 	if (!dev_blk)
829fb9ce39SBorislav Petkov 		goto free;
839fb9ce39SBorislav Petkov 
849fb9ce39SBorislav Petkov 	dev_ctl->blocks = dev_blk;
859fb9ce39SBorislav Petkov 
860d24a49eSBorislav Petkov 	if (pvt_sz) {
870d24a49eSBorislav Petkov 		pvt = kzalloc(pvt_sz, GFP_KERNEL);
889fb9ce39SBorislav Petkov 		if (!pvt)
899fb9ce39SBorislav Petkov 			goto free;
909fb9ce39SBorislav Petkov 
919fb9ce39SBorislav Petkov 		dev_ctl->pvt_info = pvt;
929fb9ce39SBorislav Petkov 	}
939fb9ce39SBorislav Petkov 
94d45e7823SDoug Thompson 	dev_ctl->dev_idx	= device_index;
95e27e3dacSDouglas Thompson 	dev_ctl->nr_instances	= nr_instances;
96e27e3dacSDouglas Thompson 
9756e61a9cSDoug Thompson 	/* Default logging of CEs and UEs */
9856e61a9cSDoug Thompson 	dev_ctl->log_ce = 1;
9956e61a9cSDoug Thompson 	dev_ctl->log_ue = 1;
10056e61a9cSDoug Thompson 
10152490c8dSDouglas Thompson 	/* Name of this edac device */
1020d24a49eSBorislav Petkov 	snprintf(dev_ctl->name, sizeof(dev_ctl->name),"%s", dev_name);
103e27e3dacSDouglas Thompson 
104e27e3dacSDouglas Thompson 	/* Initialize every Instance */
105e27e3dacSDouglas Thompson 	for (instance = 0; instance < nr_instances; instance++) {
106e27e3dacSDouglas Thompson 		inst = &dev_inst[instance];
107e27e3dacSDouglas Thompson 		inst->ctl = dev_ctl;
108e27e3dacSDouglas Thompson 		inst->nr_blocks = nr_blocks;
109e27e3dacSDouglas Thompson 		blk_p = &dev_blk[instance * nr_blocks];
110e27e3dacSDouglas Thompson 		inst->blocks = blk_p;
111e27e3dacSDouglas Thompson 
112e27e3dacSDouglas Thompson 		/* name of this instance */
1130d24a49eSBorislav Petkov 		snprintf(inst->name, sizeof(inst->name), "%s%u", dev_name, instance);
114e27e3dacSDouglas Thompson 
115e27e3dacSDouglas Thompson 		/* Initialize every block in each instance */
116079708b9SDouglas Thompson 		for (block = 0; block < nr_blocks; block++) {
117e27e3dacSDouglas Thompson 			blk = &blk_p[block];
118e27e3dacSDouglas Thompson 			blk->instance = inst;
119e27e3dacSDouglas Thompson 			snprintf(blk->name, sizeof(blk->name),
1200d24a49eSBorislav Petkov 				 "%s%d", blk_name, block + off_val);
121e27e3dacSDouglas Thompson 
122956b9ba1SJoe Perches 			edac_dbg(4, "instance=%d inst_p=%p block=#%d block_p=%p name='%s'\n",
123956b9ba1SJoe Perches 				 instance, inst, block, blk, blk->name);
124e27e3dacSDouglas Thompson 		}
125e27e3dacSDouglas Thompson 	}
126e27e3dacSDouglas Thompson 
127e27e3dacSDouglas Thompson 	/* Mark this instance as merely ALLOCATED */
128e27e3dacSDouglas Thompson 	dev_ctl->op_state = OP_ALLOC;
129e27e3dacSDouglas Thompson 
1301c3631ffSDouglas Thompson 	/*
1311c3631ffSDouglas Thompson 	 * Initialize the 'root' kobj for the edac_device controller
1321c3631ffSDouglas Thompson 	 */
1331c3631ffSDouglas Thompson 	err = edac_device_register_sysfs_main_kobj(dev_ctl);
1349fb9ce39SBorislav Petkov 	if (err)
1359fb9ce39SBorislav Petkov 		goto free;
1361c3631ffSDouglas Thompson 
1371c3631ffSDouglas Thompson 	/* at this point, the root kobj is valid, and in order to
1381c3631ffSDouglas Thompson 	 * 'free' the object, then the function:
1391c3631ffSDouglas Thompson 	 *	edac_device_unregister_sysfs_main_kobj() must be called
1401c3631ffSDouglas Thompson 	 * which will perform kobj unregistration and the actual free
1411c3631ffSDouglas Thompson 	 * will occur during the kobject callback operation
1421c3631ffSDouglas Thompson 	 */
1431c3631ffSDouglas Thompson 
144e27e3dacSDouglas Thompson 	return dev_ctl;
1459fb9ce39SBorislav Petkov 
1469fb9ce39SBorislav Petkov free:
1479fb9ce39SBorislav Petkov 	__edac_device_free_ctl_info(dev_ctl);
1489fb9ce39SBorislav Petkov 
1499fb9ce39SBorislav Petkov 	return NULL;
150e27e3dacSDouglas Thompson }
151e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_alloc_ctl_info);
152e27e3dacSDouglas Thompson 
edac_device_free_ctl_info(struct edac_device_ctl_info * ctl_info)153079708b9SDouglas Thompson void edac_device_free_ctl_info(struct edac_device_ctl_info *ctl_info)
154079708b9SDouglas Thompson {
1551c3631ffSDouglas Thompson 	edac_device_unregister_sysfs_main_kobj(ctl_info);
156e27e3dacSDouglas Thompson }
157e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_free_ctl_info);
158e27e3dacSDouglas Thompson 
159e27e3dacSDouglas Thompson /*
160e27e3dacSDouglas Thompson  * find_edac_device_by_dev
161e27e3dacSDouglas Thompson  *	scans the edac_device list for a specific 'struct device *'
16252490c8dSDouglas Thompson  *
16352490c8dSDouglas Thompson  *	lock to be held prior to call:	device_ctls_mutex
16452490c8dSDouglas Thompson  *
16552490c8dSDouglas Thompson  *	Return:
16652490c8dSDouglas Thompson  *		pointer to control structure managing 'dev'
16752490c8dSDouglas Thompson  *		NULL if not found on list
168e27e3dacSDouglas Thompson  */
find_edac_device_by_dev(struct device * dev)169079708b9SDouglas Thompson static struct edac_device_ctl_info *find_edac_device_by_dev(struct device *dev)
170e27e3dacSDouglas Thompson {
171e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *edac_dev;
172e27e3dacSDouglas Thompson 	struct list_head *item;
173e27e3dacSDouglas Thompson 
174956b9ba1SJoe Perches 	edac_dbg(0, "\n");
175e27e3dacSDouglas Thompson 
176e27e3dacSDouglas Thompson 	list_for_each(item, &edac_device_list) {
177e27e3dacSDouglas Thompson 		edac_dev = list_entry(item, struct edac_device_ctl_info, link);
178e27e3dacSDouglas Thompson 
179e27e3dacSDouglas Thompson 		if (edac_dev->dev == dev)
180e27e3dacSDouglas Thompson 			return edac_dev;
181e27e3dacSDouglas Thompson 	}
182e27e3dacSDouglas Thompson 
183e27e3dacSDouglas Thompson 	return NULL;
184e27e3dacSDouglas Thompson }
185e27e3dacSDouglas Thompson 
186e27e3dacSDouglas Thompson /*
187e27e3dacSDouglas Thompson  * add_edac_dev_to_global_list
188e27e3dacSDouglas Thompson  *	Before calling this function, caller must
189e27e3dacSDouglas Thompson  *	assign a unique value to edac_dev->dev_idx.
19052490c8dSDouglas Thompson  *
19152490c8dSDouglas Thompson  *	lock to be held prior to call:	device_ctls_mutex
19252490c8dSDouglas Thompson  *
193e27e3dacSDouglas Thompson  *	Return:
194e27e3dacSDouglas Thompson  *		0 on success
195e27e3dacSDouglas Thompson  *		1 on failure.
196e27e3dacSDouglas Thompson  */
add_edac_dev_to_global_list(struct edac_device_ctl_info * edac_dev)197e27e3dacSDouglas Thompson static int add_edac_dev_to_global_list(struct edac_device_ctl_info *edac_dev)
198e27e3dacSDouglas Thompson {
199e27e3dacSDouglas Thompson 	struct list_head *item, *insert_before;
200e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *rover;
201e27e3dacSDouglas Thompson 
202e27e3dacSDouglas Thompson 	insert_before = &edac_device_list;
203e27e3dacSDouglas Thompson 
204e27e3dacSDouglas Thompson 	/* Determine if already on the list */
20552490c8dSDouglas Thompson 	rover = find_edac_device_by_dev(edac_dev->dev);
20652490c8dSDouglas Thompson 	if (unlikely(rover != NULL))
207e27e3dacSDouglas Thompson 		goto fail0;
208e27e3dacSDouglas Thompson 
209e27e3dacSDouglas Thompson 	/* Insert in ascending order by 'dev_idx', so find position */
210e27e3dacSDouglas Thompson 	list_for_each(item, &edac_device_list) {
211e27e3dacSDouglas Thompson 		rover = list_entry(item, struct edac_device_ctl_info, link);
212e27e3dacSDouglas Thompson 
213e27e3dacSDouglas Thompson 		if (rover->dev_idx >= edac_dev->dev_idx) {
214e27e3dacSDouglas Thompson 			if (unlikely(rover->dev_idx == edac_dev->dev_idx))
215e27e3dacSDouglas Thompson 				goto fail1;
216e27e3dacSDouglas Thompson 
217e27e3dacSDouglas Thompson 			insert_before = item;
218e27e3dacSDouglas Thompson 			break;
219e27e3dacSDouglas Thompson 		}
220e27e3dacSDouglas Thompson 	}
221e27e3dacSDouglas Thompson 
222e27e3dacSDouglas Thompson 	list_add_tail_rcu(&edac_dev->link, insert_before);
223e27e3dacSDouglas Thompson 	return 0;
224e27e3dacSDouglas Thompson 
225e27e3dacSDouglas Thompson fail0:
226e27e3dacSDouglas Thompson 	edac_printk(KERN_WARNING, EDAC_MC,
227e27e3dacSDouglas Thompson 			"%s (%s) %s %s already assigned %d\n",
228281efb17SKay Sievers 			dev_name(rover->dev), edac_dev_name(rover),
229e27e3dacSDouglas Thompson 			rover->mod_name, rover->ctl_name, rover->dev_idx);
230e27e3dacSDouglas Thompson 	return 1;
231e27e3dacSDouglas Thompson 
232e27e3dacSDouglas Thompson fail1:
233e27e3dacSDouglas Thompson 	edac_printk(KERN_WARNING, EDAC_MC,
234e27e3dacSDouglas Thompson 			"bug in low-level driver: attempt to assign\n"
235079708b9SDouglas Thompson 			"    duplicate dev_idx %d in %s()\n", rover->dev_idx,
236079708b9SDouglas Thompson 			__func__);
237e27e3dacSDouglas Thompson 	return 1;
238e27e3dacSDouglas Thompson }
239e27e3dacSDouglas Thompson 
240e27e3dacSDouglas Thompson /*
241e27e3dacSDouglas Thompson  * del_edac_device_from_global_list
242e27e3dacSDouglas Thompson  */
del_edac_device_from_global_list(struct edac_device_ctl_info * edac_device)243079708b9SDouglas Thompson static void del_edac_device_from_global_list(struct edac_device_ctl_info
244079708b9SDouglas Thompson 						*edac_device)
245e27e3dacSDouglas Thompson {
246e27e3dacSDouglas Thompson 	list_del_rcu(&edac_device->link);
247e2e77098SLai Jiangshan 
248e2e77098SLai Jiangshan 	/* these are for safe removal of devices from global list while
249e2e77098SLai Jiangshan 	 * NMI handlers may be traversing list
250e2e77098SLai Jiangshan 	 */
251e2e77098SLai Jiangshan 	synchronize_rcu();
252e2e77098SLai Jiangshan 	INIT_LIST_HEAD(&edac_device->link);
253e27e3dacSDouglas Thompson }
254e27e3dacSDouglas Thompson 
255e27e3dacSDouglas Thompson /*
25681d87cb1SDave Jiang  * edac_device_workq_function
257e27e3dacSDouglas Thompson  *	performs the operation scheduled by a workq request
258bf52fa4aSDoug Thompson  *
259bf52fa4aSDoug Thompson  *	this workq is embedded within an edac_device_ctl_info
260bf52fa4aSDoug Thompson  *	structure, that needs to be polled for possible error events.
261bf52fa4aSDoug Thompson  *
262bf52fa4aSDoug Thompson  *	This operation is to acquire the list mutex lock
263f70d4a95SJiri Kosina  *	(thus preventing insertation or deletion)
264bf52fa4aSDoug Thompson  *	and then call the device's poll function IFF this device is
265bf52fa4aSDoug Thompson  *	running polled and there is a poll function defined.
266e27e3dacSDouglas Thompson  */
edac_device_workq_function(struct work_struct * work_req)26781d87cb1SDave Jiang static void edac_device_workq_function(struct work_struct *work_req)
268e27e3dacSDouglas Thompson {
269fbeb4384SJean Delvare 	struct delayed_work *d_work = to_delayed_work(work_req);
270079708b9SDouglas Thompson 	struct edac_device_ctl_info *edac_dev = to_edac_device_ctl_work(d_work);
271e27e3dacSDouglas Thompson 
2720ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
273e27e3dacSDouglas Thompson 
274d519c8d9SHarry Ciao 	/* If we are being removed, bail out immediately */
275d519c8d9SHarry Ciao 	if (edac_dev->op_state == OP_OFFLINE) {
276d519c8d9SHarry Ciao 		mutex_unlock(&device_ctls_mutex);
277d519c8d9SHarry Ciao 		return;
278d519c8d9SHarry Ciao 	}
279d519c8d9SHarry Ciao 
280e27e3dacSDouglas Thompson 	/* Only poll controllers that are running polled and have a check */
281e27e3dacSDouglas Thompson 	if ((edac_dev->op_state == OP_RUNNING_POLL) &&
282e27e3dacSDouglas Thompson 		(edac_dev->edac_check != NULL)) {
283e27e3dacSDouglas Thompson 			edac_dev->edac_check(edac_dev);
284e27e3dacSDouglas Thompson 	}
285e27e3dacSDouglas Thompson 
2860ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
287e27e3dacSDouglas Thompson 
288bf52fa4aSDoug Thompson 	/* Reschedule the workq for the next time period to start again
289bf52fa4aSDoug Thompson 	 * if the number of msec is for 1 sec, then adjust to the next
29015ed103aSDavid Mackey 	 * whole one second to save timers firing all over the period
291bf52fa4aSDoug Thompson 	 * between integral seconds
292bf52fa4aSDoug Thompson 	 */
293cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
294c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
295bf52fa4aSDoug Thompson 	else
296c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, edac_dev->delay);
297e27e3dacSDouglas Thompson }
298e27e3dacSDouglas Thompson 
299e27e3dacSDouglas Thompson /*
30081d87cb1SDave Jiang  * edac_device_workq_setup
301e27e3dacSDouglas Thompson  *	initialize a workq item for this edac_device instance
302e27e3dacSDouglas Thompson  *	passing in the new delay period in msec
303e27e3dacSDouglas Thompson  */
edac_device_workq_setup(struct edac_device_ctl_info * edac_dev,unsigned msec)304e136fa01SBorislav Petkov static void edac_device_workq_setup(struct edac_device_ctl_info *edac_dev,
30581d87cb1SDave Jiang 				    unsigned msec)
306e27e3dacSDouglas Thompson {
307956b9ba1SJoe Perches 	edac_dbg(0, "\n");
308e27e3dacSDouglas Thompson 
309bf52fa4aSDoug Thompson 	/* take the arg 'msec' and set it into the control structure
310bf52fa4aSDoug Thompson 	 * to used in the time period calculation
311bf52fa4aSDoug Thompson 	 * then calc the number of jiffies that represents
312bf52fa4aSDoug Thompson 	 */
313e27e3dacSDouglas Thompson 	edac_dev->poll_msec = msec;
314bf52fa4aSDoug Thompson 	edac_dev->delay = msecs_to_jiffies(msec);
315e27e3dacSDouglas Thompson 
31681d87cb1SDave Jiang 	INIT_DELAYED_WORK(&edac_dev->work, edac_device_workq_function);
317bf52fa4aSDoug Thompson 
318bf52fa4aSDoug Thompson 	/* optimize here for the 1 second case, which will be normal value, to
319bf52fa4aSDoug Thompson 	 * fire ON the 1 second time event. This helps reduce all sorts of
320bf52fa4aSDoug Thompson 	 * timers firing on sub-second basis, while they are happy
321bf52fa4aSDoug Thompson 	 * to fire together on the 1 second exactly
322bf52fa4aSDoug Thompson 	 */
323cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
324c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
325bf52fa4aSDoug Thompson 	else
326c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, edac_dev->delay);
327e27e3dacSDouglas Thompson }
328e27e3dacSDouglas Thompson 
329e27e3dacSDouglas Thompson /*
33081d87cb1SDave Jiang  * edac_device_workq_teardown
331e27e3dacSDouglas Thompson  *	stop the workq processing on this edac_dev
332e27e3dacSDouglas Thompson  */
edac_device_workq_teardown(struct edac_device_ctl_info * edac_dev)333e136fa01SBorislav Petkov static void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev)
334e27e3dacSDouglas Thompson {
335881f0fceSStephen Boyd 	if (!edac_dev->edac_check)
336881f0fceSStephen Boyd 		return;
337881f0fceSStephen Boyd 
338fcd5c4ddSBorislav Petkov 	edac_dev->op_state = OP_OFFLINE;
339fcd5c4ddSBorislav Petkov 
340c4cf3b45SBorislav Petkov 	edac_stop_work(&edac_dev->work);
341e27e3dacSDouglas Thompson }
342e27e3dacSDouglas Thompson 
343e27e3dacSDouglas Thompson /*
344e27e3dacSDouglas Thompson  * edac_device_reset_delay_period
345bf52fa4aSDoug Thompson  *
346bf52fa4aSDoug Thompson  *	need to stop any outstanding workq queued up at this time
347bf52fa4aSDoug Thompson  *	because we will be resetting the sleep time.
348bf52fa4aSDoug Thompson  *	Then restart the workq on the new delay
349e27e3dacSDouglas Thompson  */
edac_device_reset_delay_period(struct edac_device_ctl_info * edac_dev,unsigned long msec)350079708b9SDouglas Thompson void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev,
351e8407743SEliav Farber 				    unsigned long msec)
352e27e3dacSDouglas Thompson {
353e8407743SEliav Farber 	edac_dev->poll_msec = msec;
354e8407743SEliav Farber 	edac_dev->delay	    = msecs_to_jiffies(msec);
355e27e3dacSDouglas Thompson 
356e8407743SEliav Farber 	/* See comment in edac_device_workq_setup() above */
357cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
358e8407743SEliav Farber 		edac_mod_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
359e8407743SEliav Farber 	else
360e8407743SEliav Farber 		edac_mod_work(&edac_dev->work, edac_dev->delay);
361e27e3dacSDouglas Thompson }
362e27e3dacSDouglas Thompson 
edac_device_alloc_index(void)3631dc9b70dSHarry Ciao int edac_device_alloc_index(void)
3641dc9b70dSHarry Ciao {
3651dc9b70dSHarry Ciao 	static atomic_t device_indexes = ATOMIC_INIT(0);
3661dc9b70dSHarry Ciao 
3671dc9b70dSHarry Ciao 	return atomic_inc_return(&device_indexes) - 1;
3681dc9b70dSHarry Ciao }
3691dc9b70dSHarry Ciao EXPORT_SYMBOL_GPL(edac_device_alloc_index);
3701dc9b70dSHarry Ciao 
edac_device_add_device(struct edac_device_ctl_info * edac_dev)371d45e7823SDoug Thompson int edac_device_add_device(struct edac_device_ctl_info *edac_dev)
372e27e3dacSDouglas Thompson {
373956b9ba1SJoe Perches 	edac_dbg(0, "\n");
374e27e3dacSDouglas Thompson 
375e27e3dacSDouglas Thompson #ifdef CONFIG_EDAC_DEBUG
376e27e3dacSDouglas Thompson 	if (edac_debug_level >= 3)
377e27e3dacSDouglas Thompson 		edac_device_dump_device(edac_dev);
378e27e3dacSDouglas Thompson #endif
3790ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
380e27e3dacSDouglas Thompson 
381e27e3dacSDouglas Thompson 	if (add_edac_dev_to_global_list(edac_dev))
382e27e3dacSDouglas Thompson 		goto fail0;
383e27e3dacSDouglas Thompson 
384e27e3dacSDouglas Thompson 	/* set load time so that error rate can be tracked */
385e27e3dacSDouglas Thompson 	edac_dev->start_time = jiffies;
386e27e3dacSDouglas Thompson 
387e27e3dacSDouglas Thompson 	/* create this instance's sysfs entries */
388e27e3dacSDouglas Thompson 	if (edac_device_create_sysfs(edac_dev)) {
389e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_WARNING,
390e27e3dacSDouglas Thompson 					"failed to create sysfs device\n");
391e27e3dacSDouglas Thompson 		goto fail1;
392e27e3dacSDouglas Thompson 	}
393e27e3dacSDouglas Thompson 
394e27e3dacSDouglas Thompson 	/* If there IS a check routine, then we are running POLLED */
395e27e3dacSDouglas Thompson 	if (edac_dev->edac_check != NULL) {
396e27e3dacSDouglas Thompson 		/* This instance is NOW RUNNING */
397e27e3dacSDouglas Thompson 		edac_dev->op_state = OP_RUNNING_POLL;
398e27e3dacSDouglas Thompson 
399cec669ffSManivannan Sadhasivam 		edac_device_workq_setup(edac_dev, edac_dev->poll_msec ?: DEFAULT_POLL_INTERVAL);
400e27e3dacSDouglas Thompson 	} else {
401e27e3dacSDouglas Thompson 		edac_dev->op_state = OP_RUNNING_INTERRUPT;
402e27e3dacSDouglas Thompson 	}
403e27e3dacSDouglas Thompson 
404e27e3dacSDouglas Thompson 	/* Report action taken */
405e27e3dacSDouglas Thompson 	edac_device_printk(edac_dev, KERN_INFO,
4067270a608SRobert Richter 		"Giving out device to module %s controller %s: DEV %s (%s)\n",
4077270a608SRobert Richter 		edac_dev->mod_name, edac_dev->ctl_name, edac_dev->dev_name,
408494d0d55SDouglas Thompson 		edac_op_state_to_string(edac_dev->op_state));
409e27e3dacSDouglas Thompson 
4100ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
411e27e3dacSDouglas Thompson 	return 0;
412e27e3dacSDouglas Thompson 
413e27e3dacSDouglas Thompson fail1:
414e27e3dacSDouglas Thompson 	/* Some error, so remove the entry from the lsit */
415e27e3dacSDouglas Thompson 	del_edac_device_from_global_list(edac_dev);
416e27e3dacSDouglas Thompson 
417e27e3dacSDouglas Thompson fail0:
4180ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
419e27e3dacSDouglas Thompson 	return 1;
420e27e3dacSDouglas Thompson }
421e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_add_device);
422e27e3dacSDouglas Thompson 
edac_device_del_device(struct device * dev)423e27e3dacSDouglas Thompson struct edac_device_ctl_info *edac_device_del_device(struct device *dev)
424e27e3dacSDouglas Thompson {
425e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *edac_dev;
426e27e3dacSDouglas Thompson 
427956b9ba1SJoe Perches 	edac_dbg(0, "\n");
428e27e3dacSDouglas Thompson 
4290ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
430e27e3dacSDouglas Thompson 
43152490c8dSDouglas Thompson 	/* Find the structure on the list, if not there, then leave */
43252490c8dSDouglas Thompson 	edac_dev = find_edac_device_by_dev(dev);
43352490c8dSDouglas Thompson 	if (edac_dev == NULL) {
4340ca84761SDoug Thompson 		mutex_unlock(&device_ctls_mutex);
435e27e3dacSDouglas Thompson 		return NULL;
436e27e3dacSDouglas Thompson 	}
437e27e3dacSDouglas Thompson 
438e27e3dacSDouglas Thompson 	/* mark this instance as OFFLINE */
439e27e3dacSDouglas Thompson 	edac_dev->op_state = OP_OFFLINE;
440e27e3dacSDouglas Thompson 
441e27e3dacSDouglas Thompson 	/* deregister from global list */
442e27e3dacSDouglas Thompson 	del_edac_device_from_global_list(edac_dev);
443e27e3dacSDouglas Thompson 
4440ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
445e27e3dacSDouglas Thompson 
446d519c8d9SHarry Ciao 	/* clear workq processing on this instance */
447d519c8d9SHarry Ciao 	edac_device_workq_teardown(edac_dev);
448d519c8d9SHarry Ciao 
4491c3631ffSDouglas Thompson 	/* Tear down the sysfs entries for this instance */
4501c3631ffSDouglas Thompson 	edac_device_remove_sysfs(edac_dev);
4511c3631ffSDouglas Thompson 
452e27e3dacSDouglas Thompson 	edac_printk(KERN_INFO, EDAC_MC,
453e27e3dacSDouglas Thompson 		"Removed device %d for %s %s: DEV %s\n",
454e27e3dacSDouglas Thompson 		edac_dev->dev_idx,
45517aa7e03SStephen Rothwell 		edac_dev->mod_name, edac_dev->ctl_name, edac_dev_name(edac_dev));
456e27e3dacSDouglas Thompson 
457e27e3dacSDouglas Thompson 	return edac_dev;
458e27e3dacSDouglas Thompson }
459079708b9SDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_del_device);
460e27e3dacSDouglas Thompson 
edac_device_get_log_ce(struct edac_device_ctl_info * edac_dev)461e27e3dacSDouglas Thompson static inline int edac_device_get_log_ce(struct edac_device_ctl_info *edac_dev)
462e27e3dacSDouglas Thompson {
463e27e3dacSDouglas Thompson 	return edac_dev->log_ce;
464e27e3dacSDouglas Thompson }
465e27e3dacSDouglas Thompson 
edac_device_get_log_ue(struct edac_device_ctl_info * edac_dev)466e27e3dacSDouglas Thompson static inline int edac_device_get_log_ue(struct edac_device_ctl_info *edac_dev)
467e27e3dacSDouglas Thompson {
468e27e3dacSDouglas Thompson 	return edac_dev->log_ue;
469e27e3dacSDouglas Thompson }
470e27e3dacSDouglas Thompson 
edac_device_get_panic_on_ue(struct edac_device_ctl_info * edac_dev)471079708b9SDouglas Thompson static inline int edac_device_get_panic_on_ue(struct edac_device_ctl_info
472079708b9SDouglas Thompson 					*edac_dev)
473e27e3dacSDouglas Thompson {
474e27e3dacSDouglas Thompson 	return edac_dev->panic_on_ue;
475e27e3dacSDouglas Thompson }
476e27e3dacSDouglas Thompson 
edac_device_handle_ce_count(struct edac_device_ctl_info * edac_dev,unsigned int count,int inst_nr,int block_nr,const char * msg)4779816b4afSHanna Hawa void edac_device_handle_ce_count(struct edac_device_ctl_info *edac_dev,
4789816b4afSHanna Hawa 				 unsigned int count, int inst_nr, int block_nr,
4799816b4afSHanna Hawa 				 const char *msg)
480e27e3dacSDouglas Thompson {
481e27e3dacSDouglas Thompson 	struct edac_device_instance *instance;
482e27e3dacSDouglas Thompson 	struct edac_device_block *block = NULL;
483e27e3dacSDouglas Thompson 
4849816b4afSHanna Hawa 	if (!count)
4859816b4afSHanna Hawa 		return;
4869816b4afSHanna Hawa 
487e27e3dacSDouglas Thompson 	if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) {
488e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
489e27e3dacSDouglas Thompson 				"INTERNAL ERROR: 'instance' out of range "
490079708b9SDouglas Thompson 				"(%d >= %d)\n", inst_nr,
491079708b9SDouglas Thompson 				edac_dev->nr_instances);
492e27e3dacSDouglas Thompson 		return;
493e27e3dacSDouglas Thompson 	}
494e27e3dacSDouglas Thompson 
495e27e3dacSDouglas Thompson 	instance = edac_dev->instances + inst_nr;
496e27e3dacSDouglas Thompson 
497e27e3dacSDouglas Thompson 	if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) {
498e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
499052dfb45SDouglas Thompson 				"INTERNAL ERROR: instance %d 'block' "
500052dfb45SDouglas Thompson 				"out of range (%d >= %d)\n",
501052dfb45SDouglas Thompson 				inst_nr, block_nr,
502079708b9SDouglas Thompson 				instance->nr_blocks);
503e27e3dacSDouglas Thompson 		return;
504e27e3dacSDouglas Thompson 	}
505e27e3dacSDouglas Thompson 
506e27e3dacSDouglas Thompson 	if (instance->nr_blocks > 0) {
507e27e3dacSDouglas Thompson 		block = instance->blocks + block_nr;
5089816b4afSHanna Hawa 		block->counters.ce_count += count;
509e27e3dacSDouglas Thompson 	}
510e27e3dacSDouglas Thompson 
51125985edcSLucas De Marchi 	/* Propagate the count up the 'totals' tree */
5129816b4afSHanna Hawa 	instance->counters.ce_count += count;
5139816b4afSHanna Hawa 	edac_dev->counters.ce_count += count;
514e27e3dacSDouglas Thompson 
515e27e3dacSDouglas Thompson 	if (edac_device_get_log_ce(edac_dev))
516e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_WARNING,
5179816b4afSHanna Hawa 				   "CE: %s instance: %s block: %s count: %d '%s'\n",
518e27e3dacSDouglas Thompson 				   edac_dev->ctl_name, instance->name,
5199816b4afSHanna Hawa 				   block ? block->name : "N/A", count, msg);
520e27e3dacSDouglas Thompson }
5219816b4afSHanna Hawa EXPORT_SYMBOL_GPL(edac_device_handle_ce_count);
522e27e3dacSDouglas Thompson 
edac_device_handle_ue_count(struct edac_device_ctl_info * edac_dev,unsigned int count,int inst_nr,int block_nr,const char * msg)5239816b4afSHanna Hawa void edac_device_handle_ue_count(struct edac_device_ctl_info *edac_dev,
5249816b4afSHanna Hawa 				 unsigned int count, int inst_nr, int block_nr,
5259816b4afSHanna Hawa 				 const char *msg)
526e27e3dacSDouglas Thompson {
527e27e3dacSDouglas Thompson 	struct edac_device_instance *instance;
528e27e3dacSDouglas Thompson 	struct edac_device_block *block = NULL;
529e27e3dacSDouglas Thompson 
5309816b4afSHanna Hawa 	if (!count)
5319816b4afSHanna Hawa 		return;
5329816b4afSHanna Hawa 
533e27e3dacSDouglas Thompson 	if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) {
534e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
535e27e3dacSDouglas Thompson 				"INTERNAL ERROR: 'instance' out of range "
536079708b9SDouglas Thompson 				"(%d >= %d)\n", inst_nr,
537079708b9SDouglas Thompson 				edac_dev->nr_instances);
538e27e3dacSDouglas Thompson 		return;
539e27e3dacSDouglas Thompson 	}
540e27e3dacSDouglas Thompson 
541e27e3dacSDouglas Thompson 	instance = edac_dev->instances + inst_nr;
542e27e3dacSDouglas Thompson 
543e27e3dacSDouglas Thompson 	if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) {
544e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
545052dfb45SDouglas Thompson 				"INTERNAL ERROR: instance %d 'block' "
546052dfb45SDouglas Thompson 				"out of range (%d >= %d)\n",
547052dfb45SDouglas Thompson 				inst_nr, block_nr,
548079708b9SDouglas Thompson 				instance->nr_blocks);
549e27e3dacSDouglas Thompson 		return;
550e27e3dacSDouglas Thompson 	}
551e27e3dacSDouglas Thompson 
552e27e3dacSDouglas Thompson 	if (instance->nr_blocks > 0) {
553e27e3dacSDouglas Thompson 		block = instance->blocks + block_nr;
5549816b4afSHanna Hawa 		block->counters.ue_count += count;
555e27e3dacSDouglas Thompson 	}
556e27e3dacSDouglas Thompson 
55725985edcSLucas De Marchi 	/* Propagate the count up the 'totals' tree */
5589816b4afSHanna Hawa 	instance->counters.ue_count += count;
5599816b4afSHanna Hawa 	edac_dev->counters.ue_count += count;
560e27e3dacSDouglas Thompson 
561e27e3dacSDouglas Thompson 	if (edac_device_get_log_ue(edac_dev))
562e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_EMERG,
5639816b4afSHanna Hawa 				   "UE: %s instance: %s block: %s count: %d '%s'\n",
564e27e3dacSDouglas Thompson 				   edac_dev->ctl_name, instance->name,
5659816b4afSHanna Hawa 				   block ? block->name : "N/A", count, msg);
566e27e3dacSDouglas Thompson 
567e27e3dacSDouglas Thompson 	if (edac_device_get_panic_on_ue(edac_dev))
5689816b4afSHanna Hawa 		panic("EDAC %s: UE instance: %s block %s count: %d '%s'\n",
569e27e3dacSDouglas Thompson 		      edac_dev->ctl_name, instance->name,
5709816b4afSHanna Hawa 		      block ? block->name : "N/A", count, msg);
571e27e3dacSDouglas Thompson }
5729816b4afSHanna Hawa EXPORT_SYMBOL_GPL(edac_device_handle_ue_count);
573