xref: /openbmc/linux/drivers/edac/edac_device.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
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 
37*cec669ffSManivannan Sadhasivam /* Default workqueue processing interval on this instance, in msecs */
38*cec669ffSManivannan Sadhasivam #define DEFAULT_POLL_INTERVAL 1000
39*cec669ffSManivannan 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,struct edac_dev_sysfs_block_attribute * attrib_spec,unsigned nr_attrib,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,
590d24a49eSBorislav Petkov 			   struct edac_dev_sysfs_block_attribute *attrib_spec,
600d24a49eSBorislav Petkov 			   unsigned nr_attrib, int device_index)
61e27e3dacSDouglas Thompson {
62fd309a9dSDouglas Thompson 	struct edac_dev_sysfs_block_attribute *dev_attrib, *attrib_p, *attrib;
630d24a49eSBorislav Petkov 	struct edac_device_block *dev_blk, *blk_p, *blk;
640d24a49eSBorislav Petkov 	struct edac_device_instance *dev_inst, *inst;
650d24a49eSBorislav Petkov 	struct edac_device_ctl_info *dev_ctl;
66e27e3dacSDouglas Thompson 	unsigned instance, block, attr;
679fb9ce39SBorislav Petkov 	void *pvt;
681c3631ffSDouglas Thompson 	int err;
69e27e3dacSDouglas Thompson 
70956b9ba1SJoe Perches 	edac_dbg(4, "instances=%d blocks=%d\n", nr_instances, nr_blocks);
71e27e3dacSDouglas Thompson 
729fb9ce39SBorislav Petkov 	dev_ctl = kzalloc(sizeof(struct edac_device_ctl_info), GFP_KERNEL);
739fb9ce39SBorislav Petkov 	if (!dev_ctl)
74e27e3dacSDouglas Thompson 		return NULL;
75e27e3dacSDouglas Thompson 
7613088b65SBorislav Petkov 	dev_inst = kcalloc(nr_instances, sizeof(struct edac_device_instance), GFP_KERNEL);
779fb9ce39SBorislav Petkov 	if (!dev_inst)
789fb9ce39SBorislav Petkov 		goto free;
79e27e3dacSDouglas Thompson 
809fb9ce39SBorislav Petkov 	dev_ctl->instances = dev_inst;
819fb9ce39SBorislav Petkov 
8213088b65SBorislav Petkov 	dev_blk = kcalloc(nr_instances * nr_blocks, sizeof(struct edac_device_block), GFP_KERNEL);
839fb9ce39SBorislav Petkov 	if (!dev_blk)
849fb9ce39SBorislav Petkov 		goto free;
859fb9ce39SBorislav Petkov 
869fb9ce39SBorislav Petkov 	dev_ctl->blocks = dev_blk;
879fb9ce39SBorislav Petkov 
889fb9ce39SBorislav Petkov 	if (nr_attrib) {
8913088b65SBorislav Petkov 		dev_attrib = kcalloc(nr_attrib, sizeof(struct edac_dev_sysfs_block_attribute),
9013088b65SBorislav Petkov 				     GFP_KERNEL);
919fb9ce39SBorislav Petkov 		if (!dev_attrib)
929fb9ce39SBorislav Petkov 			goto free;
939fb9ce39SBorislav Petkov 
949fb9ce39SBorislav Petkov 		dev_ctl->attribs = dev_attrib;
959fb9ce39SBorislav Petkov 	}
969fb9ce39SBorislav Petkov 
970d24a49eSBorislav Petkov 	if (pvt_sz) {
980d24a49eSBorislav Petkov 		pvt = kzalloc(pvt_sz, GFP_KERNEL);
999fb9ce39SBorislav Petkov 		if (!pvt)
1009fb9ce39SBorislav Petkov 			goto free;
1019fb9ce39SBorislav Petkov 
1029fb9ce39SBorislav Petkov 		dev_ctl->pvt_info = pvt;
1039fb9ce39SBorislav Petkov 	}
1049fb9ce39SBorislav Petkov 
105d45e7823SDoug Thompson 	dev_ctl->dev_idx	= device_index;
106e27e3dacSDouglas Thompson 	dev_ctl->nr_instances	= nr_instances;
107e27e3dacSDouglas Thompson 
10856e61a9cSDoug Thompson 	/* Default logging of CEs and UEs */
10956e61a9cSDoug Thompson 	dev_ctl->log_ce = 1;
11056e61a9cSDoug Thompson 	dev_ctl->log_ue = 1;
11156e61a9cSDoug Thompson 
11252490c8dSDouglas Thompson 	/* Name of this edac device */
1130d24a49eSBorislav Petkov 	snprintf(dev_ctl->name, sizeof(dev_ctl->name),"%s", dev_name);
114e27e3dacSDouglas Thompson 
115e27e3dacSDouglas Thompson 	/* Initialize every Instance */
116e27e3dacSDouglas Thompson 	for (instance = 0; instance < nr_instances; instance++) {
117e27e3dacSDouglas Thompson 		inst = &dev_inst[instance];
118e27e3dacSDouglas Thompson 		inst->ctl = dev_ctl;
119e27e3dacSDouglas Thompson 		inst->nr_blocks = nr_blocks;
120e27e3dacSDouglas Thompson 		blk_p = &dev_blk[instance * nr_blocks];
121e27e3dacSDouglas Thompson 		inst->blocks = blk_p;
122e27e3dacSDouglas Thompson 
123e27e3dacSDouglas Thompson 		/* name of this instance */
1240d24a49eSBorislav Petkov 		snprintf(inst->name, sizeof(inst->name), "%s%u", dev_name, instance);
125e27e3dacSDouglas Thompson 
126e27e3dacSDouglas Thompson 		/* Initialize every block in each instance */
127079708b9SDouglas Thompson 		for (block = 0; block < nr_blocks; block++) {
128e27e3dacSDouglas Thompson 			blk = &blk_p[block];
129e27e3dacSDouglas Thompson 			blk->instance = inst;
130e27e3dacSDouglas Thompson 			snprintf(blk->name, sizeof(blk->name),
1310d24a49eSBorislav Petkov 				 "%s%d", blk_name, block + off_val);
132e27e3dacSDouglas Thompson 
133956b9ba1SJoe Perches 			edac_dbg(4, "instance=%d inst_p=%p block=#%d block_p=%p name='%s'\n",
134956b9ba1SJoe Perches 				 instance, inst, block, blk, blk->name);
135e27e3dacSDouglas Thompson 
136fd309a9dSDouglas Thompson 			/* if there are NO attributes OR no attribute pointer
137fd309a9dSDouglas Thompson 			 * then continue on to next block iteration
138e27e3dacSDouglas Thompson 			 */
139fd309a9dSDouglas Thompson 			if ((nr_attrib == 0) || (attrib_spec == NULL))
140fd309a9dSDouglas Thompson 				continue;
141fd309a9dSDouglas Thompson 
142fd309a9dSDouglas Thompson 			/* setup the attribute array for this block */
143fd309a9dSDouglas Thompson 			blk->nr_attribs = nr_attrib;
144fd309a9dSDouglas Thompson 			attrib_p = &dev_attrib[block*nr_instances*nr_attrib];
145fd309a9dSDouglas Thompson 			blk->block_attributes = attrib_p;
146fd309a9dSDouglas Thompson 
147956b9ba1SJoe Perches 			edac_dbg(4, "THIS BLOCK_ATTRIB=%p\n",
148dd23cd6eSMauro Carvalho Chehab 				 blk->block_attributes);
149b2a4ac0cSDoug Thompson 
150fd309a9dSDouglas Thompson 			/* Initialize every user specified attribute in this
151fd309a9dSDouglas Thompson 			 * block with the data the caller passed in
152b2a4ac0cSDoug Thompson 			 * Each block gets its own copy of pointers,
153b2a4ac0cSDoug Thompson 			 * and its unique 'value'
154fd309a9dSDouglas Thompson 			 */
155fd309a9dSDouglas Thompson 			for (attr = 0; attr < nr_attrib; attr++) {
156e27e3dacSDouglas Thompson 				attrib = &attrib_p[attr];
157fd309a9dSDouglas Thompson 
158b2a4ac0cSDoug Thompson 				/* populate the unique per attrib
159b2a4ac0cSDoug Thompson 				 * with the code pointers and info
160b2a4ac0cSDoug Thompson 				 */
161b2a4ac0cSDoug Thompson 				attrib->attr = attrib_spec[attr].attr;
162b2a4ac0cSDoug Thompson 				attrib->show = attrib_spec[attr].show;
163b2a4ac0cSDoug Thompson 				attrib->store = attrib_spec[attr].store;
164e27e3dacSDouglas Thompson 
165b2a4ac0cSDoug Thompson 				attrib->block = blk;	/* up link */
166b2a4ac0cSDoug Thompson 
167956b9ba1SJoe Perches 				edac_dbg(4, "alloc-attrib=%p attrib_name='%s' attrib-spec=%p spec-name=%s\n",
168dd23cd6eSMauro Carvalho Chehab 					 attrib, attrib->attr.name,
169b2a4ac0cSDoug Thompson 					 &attrib_spec[attr],
170b2a4ac0cSDoug Thompson 					 attrib_spec[attr].attr.name
171b2a4ac0cSDoug Thompson 					);
172e27e3dacSDouglas Thompson 			}
173e27e3dacSDouglas Thompson 		}
174e27e3dacSDouglas Thompson 	}
175e27e3dacSDouglas Thompson 
176e27e3dacSDouglas Thompson 	/* Mark this instance as merely ALLOCATED */
177e27e3dacSDouglas Thompson 	dev_ctl->op_state = OP_ALLOC;
178e27e3dacSDouglas Thompson 
1791c3631ffSDouglas Thompson 	/*
1801c3631ffSDouglas Thompson 	 * Initialize the 'root' kobj for the edac_device controller
1811c3631ffSDouglas Thompson 	 */
1821c3631ffSDouglas Thompson 	err = edac_device_register_sysfs_main_kobj(dev_ctl);
1839fb9ce39SBorislav Petkov 	if (err)
1849fb9ce39SBorislav Petkov 		goto free;
1851c3631ffSDouglas Thompson 
1861c3631ffSDouglas Thompson 	/* at this point, the root kobj is valid, and in order to
1871c3631ffSDouglas Thompson 	 * 'free' the object, then the function:
1881c3631ffSDouglas Thompson 	 *	edac_device_unregister_sysfs_main_kobj() must be called
1891c3631ffSDouglas Thompson 	 * which will perform kobj unregistration and the actual free
1901c3631ffSDouglas Thompson 	 * will occur during the kobject callback operation
1911c3631ffSDouglas Thompson 	 */
1921c3631ffSDouglas Thompson 
193e27e3dacSDouglas Thompson 	return dev_ctl;
1949fb9ce39SBorislav Petkov 
1959fb9ce39SBorislav Petkov free:
1969fb9ce39SBorislav Petkov 	__edac_device_free_ctl_info(dev_ctl);
1979fb9ce39SBorislav Petkov 
1989fb9ce39SBorislav Petkov 	return NULL;
199e27e3dacSDouglas Thompson }
200e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_alloc_ctl_info);
201e27e3dacSDouglas Thompson 
edac_device_free_ctl_info(struct edac_device_ctl_info * ctl_info)202079708b9SDouglas Thompson void edac_device_free_ctl_info(struct edac_device_ctl_info *ctl_info)
203079708b9SDouglas Thompson {
2041c3631ffSDouglas Thompson 	edac_device_unregister_sysfs_main_kobj(ctl_info);
205e27e3dacSDouglas Thompson }
206e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_free_ctl_info);
207e27e3dacSDouglas Thompson 
208e27e3dacSDouglas Thompson /*
209e27e3dacSDouglas Thompson  * find_edac_device_by_dev
210e27e3dacSDouglas Thompson  *	scans the edac_device list for a specific 'struct device *'
21152490c8dSDouglas Thompson  *
21252490c8dSDouglas Thompson  *	lock to be held prior to call:	device_ctls_mutex
21352490c8dSDouglas Thompson  *
21452490c8dSDouglas Thompson  *	Return:
21552490c8dSDouglas Thompson  *		pointer to control structure managing 'dev'
21652490c8dSDouglas Thompson  *		NULL if not found on list
217e27e3dacSDouglas Thompson  */
find_edac_device_by_dev(struct device * dev)218079708b9SDouglas Thompson static struct edac_device_ctl_info *find_edac_device_by_dev(struct device *dev)
219e27e3dacSDouglas Thompson {
220e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *edac_dev;
221e27e3dacSDouglas Thompson 	struct list_head *item;
222e27e3dacSDouglas Thompson 
223956b9ba1SJoe Perches 	edac_dbg(0, "\n");
224e27e3dacSDouglas Thompson 
225e27e3dacSDouglas Thompson 	list_for_each(item, &edac_device_list) {
226e27e3dacSDouglas Thompson 		edac_dev = list_entry(item, struct edac_device_ctl_info, link);
227e27e3dacSDouglas Thompson 
228e27e3dacSDouglas Thompson 		if (edac_dev->dev == dev)
229e27e3dacSDouglas Thompson 			return edac_dev;
230e27e3dacSDouglas Thompson 	}
231e27e3dacSDouglas Thompson 
232e27e3dacSDouglas Thompson 	return NULL;
233e27e3dacSDouglas Thompson }
234e27e3dacSDouglas Thompson 
235e27e3dacSDouglas Thompson /*
236e27e3dacSDouglas Thompson  * add_edac_dev_to_global_list
237e27e3dacSDouglas Thompson  *	Before calling this function, caller must
238e27e3dacSDouglas Thompson  *	assign a unique value to edac_dev->dev_idx.
23952490c8dSDouglas Thompson  *
24052490c8dSDouglas Thompson  *	lock to be held prior to call:	device_ctls_mutex
24152490c8dSDouglas Thompson  *
242e27e3dacSDouglas Thompson  *	Return:
243e27e3dacSDouglas Thompson  *		0 on success
244e27e3dacSDouglas Thompson  *		1 on failure.
245e27e3dacSDouglas Thompson  */
add_edac_dev_to_global_list(struct edac_device_ctl_info * edac_dev)246e27e3dacSDouglas Thompson static int add_edac_dev_to_global_list(struct edac_device_ctl_info *edac_dev)
247e27e3dacSDouglas Thompson {
248e27e3dacSDouglas Thompson 	struct list_head *item, *insert_before;
249e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *rover;
250e27e3dacSDouglas Thompson 
251e27e3dacSDouglas Thompson 	insert_before = &edac_device_list;
252e27e3dacSDouglas Thompson 
253e27e3dacSDouglas Thompson 	/* Determine if already on the list */
25452490c8dSDouglas Thompson 	rover = find_edac_device_by_dev(edac_dev->dev);
25552490c8dSDouglas Thompson 	if (unlikely(rover != NULL))
256e27e3dacSDouglas Thompson 		goto fail0;
257e27e3dacSDouglas Thompson 
258e27e3dacSDouglas Thompson 	/* Insert in ascending order by 'dev_idx', so find position */
259e27e3dacSDouglas Thompson 	list_for_each(item, &edac_device_list) {
260e27e3dacSDouglas Thompson 		rover = list_entry(item, struct edac_device_ctl_info, link);
261e27e3dacSDouglas Thompson 
262e27e3dacSDouglas Thompson 		if (rover->dev_idx >= edac_dev->dev_idx) {
263e27e3dacSDouglas Thompson 			if (unlikely(rover->dev_idx == edac_dev->dev_idx))
264e27e3dacSDouglas Thompson 				goto fail1;
265e27e3dacSDouglas Thompson 
266e27e3dacSDouglas Thompson 			insert_before = item;
267e27e3dacSDouglas Thompson 			break;
268e27e3dacSDouglas Thompson 		}
269e27e3dacSDouglas Thompson 	}
270e27e3dacSDouglas Thompson 
271e27e3dacSDouglas Thompson 	list_add_tail_rcu(&edac_dev->link, insert_before);
272e27e3dacSDouglas Thompson 	return 0;
273e27e3dacSDouglas Thompson 
274e27e3dacSDouglas Thompson fail0:
275e27e3dacSDouglas Thompson 	edac_printk(KERN_WARNING, EDAC_MC,
276e27e3dacSDouglas Thompson 			"%s (%s) %s %s already assigned %d\n",
277281efb17SKay Sievers 			dev_name(rover->dev), edac_dev_name(rover),
278e27e3dacSDouglas Thompson 			rover->mod_name, rover->ctl_name, rover->dev_idx);
279e27e3dacSDouglas Thompson 	return 1;
280e27e3dacSDouglas Thompson 
281e27e3dacSDouglas Thompson fail1:
282e27e3dacSDouglas Thompson 	edac_printk(KERN_WARNING, EDAC_MC,
283e27e3dacSDouglas Thompson 			"bug in low-level driver: attempt to assign\n"
284079708b9SDouglas Thompson 			"    duplicate dev_idx %d in %s()\n", rover->dev_idx,
285079708b9SDouglas Thompson 			__func__);
286e27e3dacSDouglas Thompson 	return 1;
287e27e3dacSDouglas Thompson }
288e27e3dacSDouglas Thompson 
289e27e3dacSDouglas Thompson /*
290e27e3dacSDouglas Thompson  * del_edac_device_from_global_list
291e27e3dacSDouglas Thompson  */
del_edac_device_from_global_list(struct edac_device_ctl_info * edac_device)292079708b9SDouglas Thompson static void del_edac_device_from_global_list(struct edac_device_ctl_info
293079708b9SDouglas Thompson 						*edac_device)
294e27e3dacSDouglas Thompson {
295e27e3dacSDouglas Thompson 	list_del_rcu(&edac_device->link);
296e2e77098SLai Jiangshan 
297e2e77098SLai Jiangshan 	/* these are for safe removal of devices from global list while
298e2e77098SLai Jiangshan 	 * NMI handlers may be traversing list
299e2e77098SLai Jiangshan 	 */
300e2e77098SLai Jiangshan 	synchronize_rcu();
301e2e77098SLai Jiangshan 	INIT_LIST_HEAD(&edac_device->link);
302e27e3dacSDouglas Thompson }
303e27e3dacSDouglas Thompson 
304e27e3dacSDouglas Thompson /*
30581d87cb1SDave Jiang  * edac_device_workq_function
306e27e3dacSDouglas Thompson  *	performs the operation scheduled by a workq request
307bf52fa4aSDoug Thompson  *
308bf52fa4aSDoug Thompson  *	this workq is embedded within an edac_device_ctl_info
309bf52fa4aSDoug Thompson  *	structure, that needs to be polled for possible error events.
310bf52fa4aSDoug Thompson  *
311bf52fa4aSDoug Thompson  *	This operation is to acquire the list mutex lock
312f70d4a95SJiri Kosina  *	(thus preventing insertation or deletion)
313bf52fa4aSDoug Thompson  *	and then call the device's poll function IFF this device is
314bf52fa4aSDoug Thompson  *	running polled and there is a poll function defined.
315e27e3dacSDouglas Thompson  */
edac_device_workq_function(struct work_struct * work_req)31681d87cb1SDave Jiang static void edac_device_workq_function(struct work_struct *work_req)
317e27e3dacSDouglas Thompson {
318fbeb4384SJean Delvare 	struct delayed_work *d_work = to_delayed_work(work_req);
319079708b9SDouglas Thompson 	struct edac_device_ctl_info *edac_dev = to_edac_device_ctl_work(d_work);
320e27e3dacSDouglas Thompson 
3210ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
322e27e3dacSDouglas Thompson 
323d519c8d9SHarry Ciao 	/* If we are being removed, bail out immediately */
324d519c8d9SHarry Ciao 	if (edac_dev->op_state == OP_OFFLINE) {
325d519c8d9SHarry Ciao 		mutex_unlock(&device_ctls_mutex);
326d519c8d9SHarry Ciao 		return;
327d519c8d9SHarry Ciao 	}
328d519c8d9SHarry Ciao 
329e27e3dacSDouglas Thompson 	/* Only poll controllers that are running polled and have a check */
330e27e3dacSDouglas Thompson 	if ((edac_dev->op_state == OP_RUNNING_POLL) &&
331e27e3dacSDouglas Thompson 		(edac_dev->edac_check != NULL)) {
332e27e3dacSDouglas Thompson 			edac_dev->edac_check(edac_dev);
333e27e3dacSDouglas Thompson 	}
334e27e3dacSDouglas Thompson 
3350ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
336e27e3dacSDouglas Thompson 
337bf52fa4aSDoug Thompson 	/* Reschedule the workq for the next time period to start again
338bf52fa4aSDoug Thompson 	 * if the number of msec is for 1 sec, then adjust to the next
33915ed103aSDavid Mackey 	 * whole one second to save timers firing all over the period
340bf52fa4aSDoug Thompson 	 * between integral seconds
341bf52fa4aSDoug Thompson 	 */
342*cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
343c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
344bf52fa4aSDoug Thompson 	else
345c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, edac_dev->delay);
346e27e3dacSDouglas Thompson }
347e27e3dacSDouglas Thompson 
348e27e3dacSDouglas Thompson /*
34981d87cb1SDave Jiang  * edac_device_workq_setup
350e27e3dacSDouglas Thompson  *	initialize a workq item for this edac_device instance
351e27e3dacSDouglas Thompson  *	passing in the new delay period in msec
352e27e3dacSDouglas Thompson  */
edac_device_workq_setup(struct edac_device_ctl_info * edac_dev,unsigned msec)353e136fa01SBorislav Petkov static void edac_device_workq_setup(struct edac_device_ctl_info *edac_dev,
35481d87cb1SDave Jiang 				    unsigned msec)
355e27e3dacSDouglas Thompson {
356956b9ba1SJoe Perches 	edac_dbg(0, "\n");
357e27e3dacSDouglas Thompson 
358bf52fa4aSDoug Thompson 	/* take the arg 'msec' and set it into the control structure
359bf52fa4aSDoug Thompson 	 * to used in the time period calculation
360bf52fa4aSDoug Thompson 	 * then calc the number of jiffies that represents
361bf52fa4aSDoug Thompson 	 */
362e27e3dacSDouglas Thompson 	edac_dev->poll_msec = msec;
363bf52fa4aSDoug Thompson 	edac_dev->delay = msecs_to_jiffies(msec);
364e27e3dacSDouglas Thompson 
36581d87cb1SDave Jiang 	INIT_DELAYED_WORK(&edac_dev->work, edac_device_workq_function);
366bf52fa4aSDoug Thompson 
367bf52fa4aSDoug Thompson 	/* optimize here for the 1 second case, which will be normal value, to
368bf52fa4aSDoug Thompson 	 * fire ON the 1 second time event. This helps reduce all sorts of
369bf52fa4aSDoug Thompson 	 * timers firing on sub-second basis, while they are happy
370bf52fa4aSDoug Thompson 	 * to fire together on the 1 second exactly
371bf52fa4aSDoug Thompson 	 */
372*cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
373c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
374bf52fa4aSDoug Thompson 	else
375c4cf3b45SBorislav Petkov 		edac_queue_work(&edac_dev->work, edac_dev->delay);
376e27e3dacSDouglas Thompson }
377e27e3dacSDouglas Thompson 
378e27e3dacSDouglas Thompson /*
37981d87cb1SDave Jiang  * edac_device_workq_teardown
380e27e3dacSDouglas Thompson  *	stop the workq processing on this edac_dev
381e27e3dacSDouglas Thompson  */
edac_device_workq_teardown(struct edac_device_ctl_info * edac_dev)382e136fa01SBorislav Petkov static void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev)
383e27e3dacSDouglas Thompson {
384881f0fceSStephen Boyd 	if (!edac_dev->edac_check)
385881f0fceSStephen Boyd 		return;
386881f0fceSStephen Boyd 
387fcd5c4ddSBorislav Petkov 	edac_dev->op_state = OP_OFFLINE;
388fcd5c4ddSBorislav Petkov 
389c4cf3b45SBorislav Petkov 	edac_stop_work(&edac_dev->work);
390e27e3dacSDouglas Thompson }
391e27e3dacSDouglas Thompson 
392e27e3dacSDouglas Thompson /*
393e27e3dacSDouglas Thompson  * edac_device_reset_delay_period
394bf52fa4aSDoug Thompson  *
395bf52fa4aSDoug Thompson  *	need to stop any outstanding workq queued up at this time
396bf52fa4aSDoug Thompson  *	because we will be resetting the sleep time.
397bf52fa4aSDoug Thompson  *	Then restart the workq on the new delay
398e27e3dacSDouglas Thompson  */
edac_device_reset_delay_period(struct edac_device_ctl_info * edac_dev,unsigned long msec)399079708b9SDouglas Thompson void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev,
400e8407743SEliav Farber 				    unsigned long msec)
401e27e3dacSDouglas Thompson {
402e8407743SEliav Farber 	edac_dev->poll_msec = msec;
403e8407743SEliav Farber 	edac_dev->delay	    = msecs_to_jiffies(msec);
404e27e3dacSDouglas Thompson 
405e8407743SEliav Farber 	/* See comment in edac_device_workq_setup() above */
406*cec669ffSManivannan Sadhasivam 	if (edac_dev->poll_msec == DEFAULT_POLL_INTERVAL)
407e8407743SEliav Farber 		edac_mod_work(&edac_dev->work, round_jiffies_relative(edac_dev->delay));
408e8407743SEliav Farber 	else
409e8407743SEliav Farber 		edac_mod_work(&edac_dev->work, edac_dev->delay);
410e27e3dacSDouglas Thompson }
411e27e3dacSDouglas Thompson 
edac_device_alloc_index(void)4121dc9b70dSHarry Ciao int edac_device_alloc_index(void)
4131dc9b70dSHarry Ciao {
4141dc9b70dSHarry Ciao 	static atomic_t device_indexes = ATOMIC_INIT(0);
4151dc9b70dSHarry Ciao 
4161dc9b70dSHarry Ciao 	return atomic_inc_return(&device_indexes) - 1;
4171dc9b70dSHarry Ciao }
4181dc9b70dSHarry Ciao EXPORT_SYMBOL_GPL(edac_device_alloc_index);
4191dc9b70dSHarry Ciao 
edac_device_add_device(struct edac_device_ctl_info * edac_dev)420d45e7823SDoug Thompson int edac_device_add_device(struct edac_device_ctl_info *edac_dev)
421e27e3dacSDouglas Thompson {
422956b9ba1SJoe Perches 	edac_dbg(0, "\n");
423e27e3dacSDouglas Thompson 
424e27e3dacSDouglas Thompson #ifdef CONFIG_EDAC_DEBUG
425e27e3dacSDouglas Thompson 	if (edac_debug_level >= 3)
426e27e3dacSDouglas Thompson 		edac_device_dump_device(edac_dev);
427e27e3dacSDouglas Thompson #endif
4280ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
429e27e3dacSDouglas Thompson 
430e27e3dacSDouglas Thompson 	if (add_edac_dev_to_global_list(edac_dev))
431e27e3dacSDouglas Thompson 		goto fail0;
432e27e3dacSDouglas Thompson 
433e27e3dacSDouglas Thompson 	/* set load time so that error rate can be tracked */
434e27e3dacSDouglas Thompson 	edac_dev->start_time = jiffies;
435e27e3dacSDouglas Thompson 
436e27e3dacSDouglas Thompson 	/* create this instance's sysfs entries */
437e27e3dacSDouglas Thompson 	if (edac_device_create_sysfs(edac_dev)) {
438e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_WARNING,
439e27e3dacSDouglas Thompson 					"failed to create sysfs device\n");
440e27e3dacSDouglas Thompson 		goto fail1;
441e27e3dacSDouglas Thompson 	}
442e27e3dacSDouglas Thompson 
443e27e3dacSDouglas Thompson 	/* If there IS a check routine, then we are running POLLED */
444e27e3dacSDouglas Thompson 	if (edac_dev->edac_check != NULL) {
445e27e3dacSDouglas Thompson 		/* This instance is NOW RUNNING */
446e27e3dacSDouglas Thompson 		edac_dev->op_state = OP_RUNNING_POLL;
447e27e3dacSDouglas Thompson 
448*cec669ffSManivannan Sadhasivam 		edac_device_workq_setup(edac_dev, edac_dev->poll_msec ?: DEFAULT_POLL_INTERVAL);
449e27e3dacSDouglas Thompson 	} else {
450e27e3dacSDouglas Thompson 		edac_dev->op_state = OP_RUNNING_INTERRUPT;
451e27e3dacSDouglas Thompson 	}
452e27e3dacSDouglas Thompson 
453e27e3dacSDouglas Thompson 	/* Report action taken */
454e27e3dacSDouglas Thompson 	edac_device_printk(edac_dev, KERN_INFO,
4557270a608SRobert Richter 		"Giving out device to module %s controller %s: DEV %s (%s)\n",
4567270a608SRobert Richter 		edac_dev->mod_name, edac_dev->ctl_name, edac_dev->dev_name,
457494d0d55SDouglas Thompson 		edac_op_state_to_string(edac_dev->op_state));
458e27e3dacSDouglas Thompson 
4590ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
460e27e3dacSDouglas Thompson 	return 0;
461e27e3dacSDouglas Thompson 
462e27e3dacSDouglas Thompson fail1:
463e27e3dacSDouglas Thompson 	/* Some error, so remove the entry from the lsit */
464e27e3dacSDouglas Thompson 	del_edac_device_from_global_list(edac_dev);
465e27e3dacSDouglas Thompson 
466e27e3dacSDouglas Thompson fail0:
4670ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
468e27e3dacSDouglas Thompson 	return 1;
469e27e3dacSDouglas Thompson }
470e27e3dacSDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_add_device);
471e27e3dacSDouglas Thompson 
edac_device_del_device(struct device * dev)472e27e3dacSDouglas Thompson struct edac_device_ctl_info *edac_device_del_device(struct device *dev)
473e27e3dacSDouglas Thompson {
474e27e3dacSDouglas Thompson 	struct edac_device_ctl_info *edac_dev;
475e27e3dacSDouglas Thompson 
476956b9ba1SJoe Perches 	edac_dbg(0, "\n");
477e27e3dacSDouglas Thompson 
4780ca84761SDoug Thompson 	mutex_lock(&device_ctls_mutex);
479e27e3dacSDouglas Thompson 
48052490c8dSDouglas Thompson 	/* Find the structure on the list, if not there, then leave */
48152490c8dSDouglas Thompson 	edac_dev = find_edac_device_by_dev(dev);
48252490c8dSDouglas Thompson 	if (edac_dev == NULL) {
4830ca84761SDoug Thompson 		mutex_unlock(&device_ctls_mutex);
484e27e3dacSDouglas Thompson 		return NULL;
485e27e3dacSDouglas Thompson 	}
486e27e3dacSDouglas Thompson 
487e27e3dacSDouglas Thompson 	/* mark this instance as OFFLINE */
488e27e3dacSDouglas Thompson 	edac_dev->op_state = OP_OFFLINE;
489e27e3dacSDouglas Thompson 
490e27e3dacSDouglas Thompson 	/* deregister from global list */
491e27e3dacSDouglas Thompson 	del_edac_device_from_global_list(edac_dev);
492e27e3dacSDouglas Thompson 
4930ca84761SDoug Thompson 	mutex_unlock(&device_ctls_mutex);
494e27e3dacSDouglas Thompson 
495d519c8d9SHarry Ciao 	/* clear workq processing on this instance */
496d519c8d9SHarry Ciao 	edac_device_workq_teardown(edac_dev);
497d519c8d9SHarry Ciao 
4981c3631ffSDouglas Thompson 	/* Tear down the sysfs entries for this instance */
4991c3631ffSDouglas Thompson 	edac_device_remove_sysfs(edac_dev);
5001c3631ffSDouglas Thompson 
501e27e3dacSDouglas Thompson 	edac_printk(KERN_INFO, EDAC_MC,
502e27e3dacSDouglas Thompson 		"Removed device %d for %s %s: DEV %s\n",
503e27e3dacSDouglas Thompson 		edac_dev->dev_idx,
50417aa7e03SStephen Rothwell 		edac_dev->mod_name, edac_dev->ctl_name, edac_dev_name(edac_dev));
505e27e3dacSDouglas Thompson 
506e27e3dacSDouglas Thompson 	return edac_dev;
507e27e3dacSDouglas Thompson }
508079708b9SDouglas Thompson EXPORT_SYMBOL_GPL(edac_device_del_device);
509e27e3dacSDouglas Thompson 
edac_device_get_log_ce(struct edac_device_ctl_info * edac_dev)510e27e3dacSDouglas Thompson static inline int edac_device_get_log_ce(struct edac_device_ctl_info *edac_dev)
511e27e3dacSDouglas Thompson {
512e27e3dacSDouglas Thompson 	return edac_dev->log_ce;
513e27e3dacSDouglas Thompson }
514e27e3dacSDouglas Thompson 
edac_device_get_log_ue(struct edac_device_ctl_info * edac_dev)515e27e3dacSDouglas Thompson static inline int edac_device_get_log_ue(struct edac_device_ctl_info *edac_dev)
516e27e3dacSDouglas Thompson {
517e27e3dacSDouglas Thompson 	return edac_dev->log_ue;
518e27e3dacSDouglas Thompson }
519e27e3dacSDouglas Thompson 
edac_device_get_panic_on_ue(struct edac_device_ctl_info * edac_dev)520079708b9SDouglas Thompson static inline int edac_device_get_panic_on_ue(struct edac_device_ctl_info
521079708b9SDouglas Thompson 					*edac_dev)
522e27e3dacSDouglas Thompson {
523e27e3dacSDouglas Thompson 	return edac_dev->panic_on_ue;
524e27e3dacSDouglas Thompson }
525e27e3dacSDouglas 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)5269816b4afSHanna Hawa void edac_device_handle_ce_count(struct edac_device_ctl_info *edac_dev,
5279816b4afSHanna Hawa 				 unsigned int count, int inst_nr, int block_nr,
5289816b4afSHanna Hawa 				 const char *msg)
529e27e3dacSDouglas Thompson {
530e27e3dacSDouglas Thompson 	struct edac_device_instance *instance;
531e27e3dacSDouglas Thompson 	struct edac_device_block *block = NULL;
532e27e3dacSDouglas Thompson 
5339816b4afSHanna Hawa 	if (!count)
5349816b4afSHanna Hawa 		return;
5359816b4afSHanna Hawa 
536e27e3dacSDouglas Thompson 	if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) {
537e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
538e27e3dacSDouglas Thompson 				"INTERNAL ERROR: 'instance' out of range "
539079708b9SDouglas Thompson 				"(%d >= %d)\n", inst_nr,
540079708b9SDouglas Thompson 				edac_dev->nr_instances);
541e27e3dacSDouglas Thompson 		return;
542e27e3dacSDouglas Thompson 	}
543e27e3dacSDouglas Thompson 
544e27e3dacSDouglas Thompson 	instance = edac_dev->instances + inst_nr;
545e27e3dacSDouglas Thompson 
546e27e3dacSDouglas Thompson 	if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) {
547e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
548052dfb45SDouglas Thompson 				"INTERNAL ERROR: instance %d 'block' "
549052dfb45SDouglas Thompson 				"out of range (%d >= %d)\n",
550052dfb45SDouglas Thompson 				inst_nr, block_nr,
551079708b9SDouglas Thompson 				instance->nr_blocks);
552e27e3dacSDouglas Thompson 		return;
553e27e3dacSDouglas Thompson 	}
554e27e3dacSDouglas Thompson 
555e27e3dacSDouglas Thompson 	if (instance->nr_blocks > 0) {
556e27e3dacSDouglas Thompson 		block = instance->blocks + block_nr;
5579816b4afSHanna Hawa 		block->counters.ce_count += count;
558e27e3dacSDouglas Thompson 	}
559e27e3dacSDouglas Thompson 
56025985edcSLucas De Marchi 	/* Propagate the count up the 'totals' tree */
5619816b4afSHanna Hawa 	instance->counters.ce_count += count;
5629816b4afSHanna Hawa 	edac_dev->counters.ce_count += count;
563e27e3dacSDouglas Thompson 
564e27e3dacSDouglas Thompson 	if (edac_device_get_log_ce(edac_dev))
565e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_WARNING,
5669816b4afSHanna Hawa 				   "CE: %s instance: %s block: %s count: %d '%s'\n",
567e27e3dacSDouglas Thompson 				   edac_dev->ctl_name, instance->name,
5689816b4afSHanna Hawa 				   block ? block->name : "N/A", count, msg);
569e27e3dacSDouglas Thompson }
5709816b4afSHanna Hawa EXPORT_SYMBOL_GPL(edac_device_handle_ce_count);
571e27e3dacSDouglas 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)5729816b4afSHanna Hawa void edac_device_handle_ue_count(struct edac_device_ctl_info *edac_dev,
5739816b4afSHanna Hawa 				 unsigned int count, int inst_nr, int block_nr,
5749816b4afSHanna Hawa 				 const char *msg)
575e27e3dacSDouglas Thompson {
576e27e3dacSDouglas Thompson 	struct edac_device_instance *instance;
577e27e3dacSDouglas Thompson 	struct edac_device_block *block = NULL;
578e27e3dacSDouglas Thompson 
5799816b4afSHanna Hawa 	if (!count)
5809816b4afSHanna Hawa 		return;
5819816b4afSHanna Hawa 
582e27e3dacSDouglas Thompson 	if ((inst_nr >= edac_dev->nr_instances) || (inst_nr < 0)) {
583e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
584e27e3dacSDouglas Thompson 				"INTERNAL ERROR: 'instance' out of range "
585079708b9SDouglas Thompson 				"(%d >= %d)\n", inst_nr,
586079708b9SDouglas Thompson 				edac_dev->nr_instances);
587e27e3dacSDouglas Thompson 		return;
588e27e3dacSDouglas Thompson 	}
589e27e3dacSDouglas Thompson 
590e27e3dacSDouglas Thompson 	instance = edac_dev->instances + inst_nr;
591e27e3dacSDouglas Thompson 
592e27e3dacSDouglas Thompson 	if ((block_nr >= instance->nr_blocks) || (block_nr < 0)) {
593e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_ERR,
594052dfb45SDouglas Thompson 				"INTERNAL ERROR: instance %d 'block' "
595052dfb45SDouglas Thompson 				"out of range (%d >= %d)\n",
596052dfb45SDouglas Thompson 				inst_nr, block_nr,
597079708b9SDouglas Thompson 				instance->nr_blocks);
598e27e3dacSDouglas Thompson 		return;
599e27e3dacSDouglas Thompson 	}
600e27e3dacSDouglas Thompson 
601e27e3dacSDouglas Thompson 	if (instance->nr_blocks > 0) {
602e27e3dacSDouglas Thompson 		block = instance->blocks + block_nr;
6039816b4afSHanna Hawa 		block->counters.ue_count += count;
604e27e3dacSDouglas Thompson 	}
605e27e3dacSDouglas Thompson 
60625985edcSLucas De Marchi 	/* Propagate the count up the 'totals' tree */
6079816b4afSHanna Hawa 	instance->counters.ue_count += count;
6089816b4afSHanna Hawa 	edac_dev->counters.ue_count += count;
609e27e3dacSDouglas Thompson 
610e27e3dacSDouglas Thompson 	if (edac_device_get_log_ue(edac_dev))
611e27e3dacSDouglas Thompson 		edac_device_printk(edac_dev, KERN_EMERG,
6129816b4afSHanna Hawa 				   "UE: %s instance: %s block: %s count: %d '%s'\n",
613e27e3dacSDouglas Thompson 				   edac_dev->ctl_name, instance->name,
6149816b4afSHanna Hawa 				   block ? block->name : "N/A", count, msg);
615e27e3dacSDouglas Thompson 
616e27e3dacSDouglas Thompson 	if (edac_device_get_panic_on_ue(edac_dev))
6179816b4afSHanna Hawa 		panic("EDAC %s: UE instance: %s block %s count: %d '%s'\n",
618e27e3dacSDouglas Thompson 		      edac_dev->ctl_name, instance->name,
6199816b4afSHanna Hawa 		      block ? block->name : "N/A", count, msg);
620e27e3dacSDouglas Thompson }
6219816b4afSHanna Hawa EXPORT_SYMBOL_GPL(edac_device_handle_ue_count);
622