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