diff options
author | Douglas Thompson <dougthompson@xmission.com> | 2007-07-19 04:49:33 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-19 13:04:53 -0400 |
commit | 7c9281d76c1c0b130f79d5fc021084e9749959d4 (patch) | |
tree | 8e54412e8dc529e8bf755633ebe71e35183353d0 /drivers/edac/edac_mc_sysfs.c | |
parent | d56933e018b14fc7cad322f413eecc6cb6edf12e (diff) |
drivers/edac: split out functions to unique files
This is a large patch to refactor the original EDAC module in the kernel
and to break it up into better file granularity, such that each source
file contains a given subsystem of the EDAC CORE.
Originally, the EDAC 'core' was contained in one source file: edac_mc.c
with it corresponding edac_mc.h file.
Now, there are the following files:
edac_module.c The main module init/exit function and other overhead
edac_mc.c Code handling the edac_mc class of object
edac_mc_sysfs.c Code handling for sysfs presentation
edac_pci_sysfs.c Code handling for PCI sysfs presentation
edac_core.h CORE .h include file for 'edac_mc' and 'edac_device' drivers
edac_module.h Internal CORE .h include file
This forms a foundation upon which a later patch can create the 'edac_device'
class of object code in a new file 'edac_device.c'.
Signed-off-by: Douglas Thompson <dougthompson@xmission.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/edac/edac_mc_sysfs.c')
-rw-r--r-- | drivers/edac/edac_mc_sysfs.c | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c new file mode 100644 index 000000000000..4a5e335f61d3 --- /dev/null +++ b/drivers/edac/edac_mc_sysfs.c | |||
@@ -0,0 +1,889 @@ | |||
1 | /* | ||
2 | * edac_mc kernel module | ||
3 | * (C) 2005, 2006 Linux Networx (http://lnxi.com) | ||
4 | * This file may be distributed under the terms of the | ||
5 | * GNU General Public License. | ||
6 | * | ||
7 | * Written Doug Thompson <norsk5@xmission.com> | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/sysdev.h> | ||
13 | #include <linux/ctype.h> | ||
14 | |||
15 | #include "edac_mc.h" | ||
16 | #include "edac_module.h" | ||
17 | |||
18 | /* MC EDAC Controls, setable by module parameter, and sysfs */ | ||
19 | static int log_ue = 1; | ||
20 | static int log_ce = 1; | ||
21 | static int panic_on_ue; | ||
22 | static int poll_msec = 1000; | ||
23 | |||
24 | /* Getter functions for above */ | ||
25 | int edac_get_log_ue() | ||
26 | { | ||
27 | return log_ue; | ||
28 | } | ||
29 | |||
30 | int edac_get_log_ce() | ||
31 | { | ||
32 | return log_ce; | ||
33 | } | ||
34 | |||
35 | int edac_get_panic_on_ue() | ||
36 | { | ||
37 | return panic_on_ue; | ||
38 | } | ||
39 | |||
40 | int edac_get_poll_msec() | ||
41 | { | ||
42 | return poll_msec; | ||
43 | } | ||
44 | |||
45 | /* Parameter declarations for above */ | ||
46 | module_param(panic_on_ue, int, 0644); | ||
47 | MODULE_PARM_DESC(panic_on_ue, "Panic on uncorrected error: 0=off 1=on"); | ||
48 | module_param(log_ue, int, 0644); | ||
49 | MODULE_PARM_DESC(log_ue, "Log uncorrectable error to console: 0=off 1=on"); | ||
50 | module_param(log_ce, int, 0644); | ||
51 | MODULE_PARM_DESC(log_ce, "Log correctable error to console: 0=off 1=on"); | ||
52 | module_param(poll_msec, int, 0644); | ||
53 | MODULE_PARM_DESC(poll_msec, "Polling period in milliseconds"); | ||
54 | |||
55 | |||
56 | /* | ||
57 | * various constants for Memory Controllers | ||
58 | */ | ||
59 | static const char *mem_types[] = { | ||
60 | [MEM_EMPTY] = "Empty", | ||
61 | [MEM_RESERVED] = "Reserved", | ||
62 | [MEM_UNKNOWN] = "Unknown", | ||
63 | [MEM_FPM] = "FPM", | ||
64 | [MEM_EDO] = "EDO", | ||
65 | [MEM_BEDO] = "BEDO", | ||
66 | [MEM_SDR] = "Unbuffered-SDR", | ||
67 | [MEM_RDR] = "Registered-SDR", | ||
68 | [MEM_DDR] = "Unbuffered-DDR", | ||
69 | [MEM_RDDR] = "Registered-DDR", | ||
70 | [MEM_RMBS] = "RMBS" | ||
71 | }; | ||
72 | |||
73 | static const char *dev_types[] = { | ||
74 | [DEV_UNKNOWN] = "Unknown", | ||
75 | [DEV_X1] = "x1", | ||
76 | [DEV_X2] = "x2", | ||
77 | [DEV_X4] = "x4", | ||
78 | [DEV_X8] = "x8", | ||
79 | [DEV_X16] = "x16", | ||
80 | [DEV_X32] = "x32", | ||
81 | [DEV_X64] = "x64" | ||
82 | }; | ||
83 | |||
84 | static const char *edac_caps[] = { | ||
85 | [EDAC_UNKNOWN] = "Unknown", | ||
86 | [EDAC_NONE] = "None", | ||
87 | [EDAC_RESERVED] = "Reserved", | ||
88 | [EDAC_PARITY] = "PARITY", | ||
89 | [EDAC_EC] = "EC", | ||
90 | [EDAC_SECDED] = "SECDED", | ||
91 | [EDAC_S2ECD2ED] = "S2ECD2ED", | ||
92 | [EDAC_S4ECD4ED] = "S4ECD4ED", | ||
93 | [EDAC_S8ECD8ED] = "S8ECD8ED", | ||
94 | [EDAC_S16ECD16ED] = "S16ECD16ED" | ||
95 | }; | ||
96 | |||
97 | /* | ||
98 | * sysfs object: /sys/devices/system/edac | ||
99 | * need to export to other files in this modules | ||
100 | */ | ||
101 | struct sysdev_class edac_class = { | ||
102 | set_kset_name("edac"), | ||
103 | }; | ||
104 | |||
105 | /* sysfs object: | ||
106 | * /sys/devices/system/edac/mc | ||
107 | */ | ||
108 | static struct kobject edac_memctrl_kobj; | ||
109 | |||
110 | /* We use these to wait for the reference counts on edac_memctrl_kobj and | ||
111 | * edac_pci_kobj to reach 0. | ||
112 | */ | ||
113 | static struct completion edac_memctrl_kobj_complete; | ||
114 | |||
115 | /* | ||
116 | * /sys/devices/system/edac/mc; | ||
117 | * data structures and methods | ||
118 | */ | ||
119 | static ssize_t memctrl_int_show(void *ptr, char *buffer) | ||
120 | { | ||
121 | int *value = (int*) ptr; | ||
122 | return sprintf(buffer, "%u\n", *value); | ||
123 | } | ||
124 | |||
125 | static ssize_t memctrl_int_store(void *ptr, const char *buffer, size_t count) | ||
126 | { | ||
127 | int *value = (int*) ptr; | ||
128 | |||
129 | if (isdigit(*buffer)) | ||
130 | *value = simple_strtoul(buffer, NULL, 0); | ||
131 | |||
132 | return count; | ||
133 | } | ||
134 | |||
135 | struct memctrl_dev_attribute { | ||
136 | struct attribute attr; | ||
137 | void *value; | ||
138 | ssize_t (*show)(void *,char *); | ||
139 | ssize_t (*store)(void *, const char *, size_t); | ||
140 | }; | ||
141 | |||
142 | /* Set of show/store abstract level functions for memory control object */ | ||
143 | static ssize_t memctrl_dev_show(struct kobject *kobj, | ||
144 | struct attribute *attr, char *buffer) | ||
145 | { | ||
146 | struct memctrl_dev_attribute *memctrl_dev; | ||
147 | memctrl_dev = (struct memctrl_dev_attribute*)attr; | ||
148 | |||
149 | if (memctrl_dev->show) | ||
150 | return memctrl_dev->show(memctrl_dev->value, buffer); | ||
151 | |||
152 | return -EIO; | ||
153 | } | ||
154 | |||
155 | static ssize_t memctrl_dev_store(struct kobject *kobj, struct attribute *attr, | ||
156 | const char *buffer, size_t count) | ||
157 | { | ||
158 | struct memctrl_dev_attribute *memctrl_dev; | ||
159 | memctrl_dev = (struct memctrl_dev_attribute*)attr; | ||
160 | |||
161 | if (memctrl_dev->store) | ||
162 | return memctrl_dev->store(memctrl_dev->value, buffer, count); | ||
163 | |||
164 | return -EIO; | ||
165 | } | ||
166 | |||
167 | static struct sysfs_ops memctrlfs_ops = { | ||
168 | .show = memctrl_dev_show, | ||
169 | .store = memctrl_dev_store | ||
170 | }; | ||
171 | |||
172 | #define MEMCTRL_ATTR(_name,_mode,_show,_store) \ | ||
173 | static struct memctrl_dev_attribute attr_##_name = { \ | ||
174 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | ||
175 | .value = &_name, \ | ||
176 | .show = _show, \ | ||
177 | .store = _store, \ | ||
178 | }; | ||
179 | |||
180 | #define MEMCTRL_STRING_ATTR(_name,_data,_mode,_show,_store) \ | ||
181 | static struct memctrl_dev_attribute attr_##_name = { \ | ||
182 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | ||
183 | .value = _data, \ | ||
184 | .show = _show, \ | ||
185 | .store = _store, \ | ||
186 | }; | ||
187 | |||
188 | /* csrow<id> control files */ | ||
189 | MEMCTRL_ATTR(panic_on_ue,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); | ||
190 | MEMCTRL_ATTR(log_ue,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); | ||
191 | MEMCTRL_ATTR(log_ce,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); | ||
192 | MEMCTRL_ATTR(poll_msec,S_IRUGO|S_IWUSR,memctrl_int_show,memctrl_int_store); | ||
193 | |||
194 | /* Base Attributes of the memory ECC object */ | ||
195 | static struct memctrl_dev_attribute *memctrl_attr[] = { | ||
196 | &attr_panic_on_ue, | ||
197 | &attr_log_ue, | ||
198 | &attr_log_ce, | ||
199 | &attr_poll_msec, | ||
200 | NULL, | ||
201 | }; | ||
202 | |||
203 | /* Main MC kobject release() function */ | ||
204 | static void edac_memctrl_master_release(struct kobject *kobj) | ||
205 | { | ||
206 | debugf1("%s()\n", __func__); | ||
207 | complete(&edac_memctrl_kobj_complete); | ||
208 | } | ||
209 | |||
210 | static struct kobj_type ktype_memctrl = { | ||
211 | .release = edac_memctrl_master_release, | ||
212 | .sysfs_ops = &memctrlfs_ops, | ||
213 | .default_attrs = (struct attribute **) memctrl_attr, | ||
214 | }; | ||
215 | |||
216 | /* Initialize the main sysfs entries for edac: | ||
217 | * /sys/devices/system/edac | ||
218 | * | ||
219 | * and children | ||
220 | * | ||
221 | * Return: 0 SUCCESS | ||
222 | * !0 FAILURE | ||
223 | */ | ||
224 | int edac_sysfs_memctrl_setup(void) | ||
225 | { | ||
226 | int err = 0; | ||
227 | |||
228 | debugf1("%s()\n", __func__); | ||
229 | |||
230 | /* create the /sys/devices/system/edac directory */ | ||
231 | err = sysdev_class_register(&edac_class); | ||
232 | |||
233 | if (err) { | ||
234 | debugf1("%s() error=%d\n", __func__, err); | ||
235 | return err; | ||
236 | } | ||
237 | |||
238 | /* Init the MC's kobject */ | ||
239 | memset(&edac_memctrl_kobj, 0, sizeof (edac_memctrl_kobj)); | ||
240 | edac_memctrl_kobj.parent = &edac_class.kset.kobj; | ||
241 | edac_memctrl_kobj.ktype = &ktype_memctrl; | ||
242 | |||
243 | /* generate sysfs "..../edac/mc" */ | ||
244 | err = kobject_set_name(&edac_memctrl_kobj,"mc"); | ||
245 | |||
246 | if (err) | ||
247 | goto fail; | ||
248 | |||
249 | /* FIXME: maybe new sysdev_create_subdir() */ | ||
250 | err = kobject_register(&edac_memctrl_kobj); | ||
251 | |||
252 | if (err) { | ||
253 | debugf1("Failed to register '.../edac/mc'\n"); | ||
254 | goto fail; | ||
255 | } | ||
256 | |||
257 | debugf1("Registered '.../edac/mc' kobject\n"); | ||
258 | |||
259 | return 0; | ||
260 | |||
261 | fail: | ||
262 | sysdev_class_unregister(&edac_class); | ||
263 | return err; | ||
264 | } | ||
265 | |||
266 | /* | ||
267 | * MC teardown: | ||
268 | * the '..../edac/mc' kobject followed by '..../edac' itself | ||
269 | */ | ||
270 | void edac_sysfs_memctrl_teardown(void) | ||
271 | { | ||
272 | debugf0("MC: " __FILE__ ": %s()\n", __func__); | ||
273 | |||
274 | /* Unregister the MC's kobject and wait for reference count to reach 0. | ||
275 | */ | ||
276 | init_completion(&edac_memctrl_kobj_complete); | ||
277 | kobject_unregister(&edac_memctrl_kobj); | ||
278 | wait_for_completion(&edac_memctrl_kobj_complete); | ||
279 | |||
280 | /* Unregister the 'edac' object */ | ||
281 | sysdev_class_unregister(&edac_class); | ||
282 | } | ||
283 | |||
284 | |||
285 | /* EDAC sysfs CSROW data structures and methods | ||
286 | */ | ||
287 | |||
288 | /* Set of more default csrow<id> attribute show/store functions */ | ||
289 | static ssize_t csrow_ue_count_show(struct csrow_info *csrow, char *data, int private) | ||
290 | { | ||
291 | return sprintf(data,"%u\n", csrow->ue_count); | ||
292 | } | ||
293 | |||
294 | static ssize_t csrow_ce_count_show(struct csrow_info *csrow, char *data, int private) | ||
295 | { | ||
296 | return sprintf(data,"%u\n", csrow->ce_count); | ||
297 | } | ||
298 | |||
299 | static ssize_t csrow_size_show(struct csrow_info *csrow, char *data, int private) | ||
300 | { | ||
301 | return sprintf(data,"%u\n", PAGES_TO_MiB(csrow->nr_pages)); | ||
302 | } | ||
303 | |||
304 | static ssize_t csrow_mem_type_show(struct csrow_info *csrow, char *data, int private) | ||
305 | { | ||
306 | return sprintf(data,"%s\n", mem_types[csrow->mtype]); | ||
307 | } | ||
308 | |||
309 | static ssize_t csrow_dev_type_show(struct csrow_info *csrow, char *data, int private) | ||
310 | { | ||
311 | return sprintf(data,"%s\n", dev_types[csrow->dtype]); | ||
312 | } | ||
313 | |||
314 | static ssize_t csrow_edac_mode_show(struct csrow_info *csrow, char *data, int private) | ||
315 | { | ||
316 | return sprintf(data,"%s\n", edac_caps[csrow->edac_mode]); | ||
317 | } | ||
318 | |||
319 | /* show/store functions for DIMM Label attributes */ | ||
320 | static ssize_t channel_dimm_label_show(struct csrow_info *csrow, | ||
321 | char *data, int channel) | ||
322 | { | ||
323 | return snprintf(data, EDAC_MC_LABEL_LEN,"%s", | ||
324 | csrow->channels[channel].label); | ||
325 | } | ||
326 | |||
327 | static ssize_t channel_dimm_label_store(struct csrow_info *csrow, | ||
328 | const char *data, | ||
329 | size_t count, | ||
330 | int channel) | ||
331 | { | ||
332 | ssize_t max_size = 0; | ||
333 | |||
334 | max_size = min((ssize_t)count,(ssize_t)EDAC_MC_LABEL_LEN-1); | ||
335 | strncpy(csrow->channels[channel].label, data, max_size); | ||
336 | csrow->channels[channel].label[max_size] = '\0'; | ||
337 | |||
338 | return max_size; | ||
339 | } | ||
340 | |||
341 | /* show function for dynamic chX_ce_count attribute */ | ||
342 | static ssize_t channel_ce_count_show(struct csrow_info *csrow, | ||
343 | char *data, | ||
344 | int channel) | ||
345 | { | ||
346 | return sprintf(data, "%u\n", csrow->channels[channel].ce_count); | ||
347 | } | ||
348 | |||
349 | /* csrow specific attribute structure */ | ||
350 | struct csrowdev_attribute { | ||
351 | struct attribute attr; | ||
352 | ssize_t (*show)(struct csrow_info *,char *,int); | ||
353 | ssize_t (*store)(struct csrow_info *, const char *,size_t,int); | ||
354 | int private; | ||
355 | }; | ||
356 | |||
357 | #define to_csrow(k) container_of(k, struct csrow_info, kobj) | ||
358 | #define to_csrowdev_attr(a) container_of(a, struct csrowdev_attribute, attr) | ||
359 | |||
360 | /* Set of show/store higher level functions for default csrow attributes */ | ||
361 | static ssize_t csrowdev_show(struct kobject *kobj, | ||
362 | struct attribute *attr, | ||
363 | char *buffer) | ||
364 | { | ||
365 | struct csrow_info *csrow = to_csrow(kobj); | ||
366 | struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); | ||
367 | |||
368 | if (csrowdev_attr->show) | ||
369 | return csrowdev_attr->show(csrow, | ||
370 | buffer, | ||
371 | csrowdev_attr->private); | ||
372 | return -EIO; | ||
373 | } | ||
374 | |||
375 | static ssize_t csrowdev_store(struct kobject *kobj, struct attribute *attr, | ||
376 | const char *buffer, size_t count) | ||
377 | { | ||
378 | struct csrow_info *csrow = to_csrow(kobj); | ||
379 | struct csrowdev_attribute * csrowdev_attr = to_csrowdev_attr(attr); | ||
380 | |||
381 | if (csrowdev_attr->store) | ||
382 | return csrowdev_attr->store(csrow, | ||
383 | buffer, | ||
384 | count, | ||
385 | csrowdev_attr->private); | ||
386 | return -EIO; | ||
387 | } | ||
388 | |||
389 | static struct sysfs_ops csrowfs_ops = { | ||
390 | .show = csrowdev_show, | ||
391 | .store = csrowdev_store | ||
392 | }; | ||
393 | |||
394 | #define CSROWDEV_ATTR(_name,_mode,_show,_store,_private) \ | ||
395 | static struct csrowdev_attribute attr_##_name = { \ | ||
396 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | ||
397 | .show = _show, \ | ||
398 | .store = _store, \ | ||
399 | .private = _private, \ | ||
400 | }; | ||
401 | |||
402 | /* default cwrow<id>/attribute files */ | ||
403 | CSROWDEV_ATTR(size_mb,S_IRUGO,csrow_size_show,NULL,0); | ||
404 | CSROWDEV_ATTR(dev_type,S_IRUGO,csrow_dev_type_show,NULL,0); | ||
405 | CSROWDEV_ATTR(mem_type,S_IRUGO,csrow_mem_type_show,NULL,0); | ||
406 | CSROWDEV_ATTR(edac_mode,S_IRUGO,csrow_edac_mode_show,NULL,0); | ||
407 | CSROWDEV_ATTR(ue_count,S_IRUGO,csrow_ue_count_show,NULL,0); | ||
408 | CSROWDEV_ATTR(ce_count,S_IRUGO,csrow_ce_count_show,NULL,0); | ||
409 | |||
410 | /* default attributes of the CSROW<id> object */ | ||
411 | static struct csrowdev_attribute *default_csrow_attr[] = { | ||
412 | &attr_dev_type, | ||
413 | &attr_mem_type, | ||
414 | &attr_edac_mode, | ||
415 | &attr_size_mb, | ||
416 | &attr_ue_count, | ||
417 | &attr_ce_count, | ||
418 | NULL, | ||
419 | }; | ||
420 | |||
421 | |||
422 | /* possible dynamic channel DIMM Label attribute files */ | ||
423 | CSROWDEV_ATTR(ch0_dimm_label,S_IRUGO|S_IWUSR, | ||
424 | channel_dimm_label_show, | ||
425 | channel_dimm_label_store, | ||
426 | 0 ); | ||
427 | CSROWDEV_ATTR(ch1_dimm_label,S_IRUGO|S_IWUSR, | ||
428 | channel_dimm_label_show, | ||
429 | channel_dimm_label_store, | ||
430 | 1 ); | ||
431 | CSROWDEV_ATTR(ch2_dimm_label,S_IRUGO|S_IWUSR, | ||
432 | channel_dimm_label_show, | ||
433 | channel_dimm_label_store, | ||
434 | 2 ); | ||
435 | CSROWDEV_ATTR(ch3_dimm_label,S_IRUGO|S_IWUSR, | ||
436 | channel_dimm_label_show, | ||
437 | channel_dimm_label_store, | ||
438 | 3 ); | ||
439 | CSROWDEV_ATTR(ch4_dimm_label,S_IRUGO|S_IWUSR, | ||
440 | channel_dimm_label_show, | ||
441 | channel_dimm_label_store, | ||
442 | 4 ); | ||
443 | CSROWDEV_ATTR(ch5_dimm_label,S_IRUGO|S_IWUSR, | ||
444 | channel_dimm_label_show, | ||
445 | channel_dimm_label_store, | ||
446 | 5 ); | ||
447 | |||
448 | /* Total possible dynamic DIMM Label attribute file table */ | ||
449 | static struct csrowdev_attribute *dynamic_csrow_dimm_attr[] = { | ||
450 | &attr_ch0_dimm_label, | ||
451 | &attr_ch1_dimm_label, | ||
452 | &attr_ch2_dimm_label, | ||
453 | &attr_ch3_dimm_label, | ||
454 | &attr_ch4_dimm_label, | ||
455 | &attr_ch5_dimm_label | ||
456 | }; | ||
457 | |||
458 | /* possible dynamic channel ce_count attribute files */ | ||
459 | CSROWDEV_ATTR(ch0_ce_count,S_IRUGO|S_IWUSR, | ||
460 | channel_ce_count_show, | ||
461 | NULL, | ||
462 | 0 ); | ||
463 | CSROWDEV_ATTR(ch1_ce_count,S_IRUGO|S_IWUSR, | ||
464 | channel_ce_count_show, | ||
465 | NULL, | ||
466 | 1 ); | ||
467 | CSROWDEV_ATTR(ch2_ce_count,S_IRUGO|S_IWUSR, | ||
468 | channel_ce_count_show, | ||
469 | NULL, | ||
470 | 2 ); | ||
471 | CSROWDEV_ATTR(ch3_ce_count,S_IRUGO|S_IWUSR, | ||
472 | channel_ce_count_show, | ||
473 | NULL, | ||
474 | 3 ); | ||
475 | CSROWDEV_ATTR(ch4_ce_count,S_IRUGO|S_IWUSR, | ||
476 | channel_ce_count_show, | ||
477 | NULL, | ||
478 | 4 ); | ||
479 | CSROWDEV_ATTR(ch5_ce_count,S_IRUGO|S_IWUSR, | ||
480 | channel_ce_count_show, | ||
481 | NULL, | ||
482 | 5 ); | ||
483 | |||
484 | /* Total possible dynamic ce_count attribute file table */ | ||
485 | static struct csrowdev_attribute *dynamic_csrow_ce_count_attr[] = { | ||
486 | &attr_ch0_ce_count, | ||
487 | &attr_ch1_ce_count, | ||
488 | &attr_ch2_ce_count, | ||
489 | &attr_ch3_ce_count, | ||
490 | &attr_ch4_ce_count, | ||
491 | &attr_ch5_ce_count | ||
492 | }; | ||
493 | |||
494 | |||
495 | #define EDAC_NR_CHANNELS 6 | ||
496 | |||
497 | /* Create dynamic CHANNEL files, indexed by 'chan', under specifed CSROW */ | ||
498 | static int edac_create_channel_files(struct kobject *kobj, int chan) | ||
499 | { | ||
500 | int err=-ENODEV; | ||
501 | |||
502 | if (chan >= EDAC_NR_CHANNELS) | ||
503 | return err; | ||
504 | |||
505 | /* create the DIMM label attribute file */ | ||
506 | err = sysfs_create_file(kobj, | ||
507 | (struct attribute *) dynamic_csrow_dimm_attr[chan]); | ||
508 | |||
509 | if (!err) { | ||
510 | /* create the CE Count attribute file */ | ||
511 | err = sysfs_create_file(kobj, | ||
512 | (struct attribute *) dynamic_csrow_ce_count_attr[chan]); | ||
513 | } else { | ||
514 | debugf1("%s() dimm labels and ce_count files created", __func__); | ||
515 | } | ||
516 | |||
517 | return err; | ||
518 | } | ||
519 | |||
520 | /* No memory to release for this kobj */ | ||
521 | static void edac_csrow_instance_release(struct kobject *kobj) | ||
522 | { | ||
523 | struct csrow_info *cs; | ||
524 | |||
525 | cs = container_of(kobj, struct csrow_info, kobj); | ||
526 | complete(&cs->kobj_complete); | ||
527 | } | ||
528 | |||
529 | /* the kobj_type instance for a CSROW */ | ||
530 | static struct kobj_type ktype_csrow = { | ||
531 | .release = edac_csrow_instance_release, | ||
532 | .sysfs_ops = &csrowfs_ops, | ||
533 | .default_attrs = (struct attribute **) default_csrow_attr, | ||
534 | }; | ||
535 | |||
536 | /* Create a CSROW object under specifed edac_mc_device */ | ||
537 | static int edac_create_csrow_object( | ||
538 | struct kobject *edac_mci_kobj, | ||
539 | struct csrow_info *csrow, | ||
540 | int index) | ||
541 | { | ||
542 | int err = 0; | ||
543 | int chan; | ||
544 | |||
545 | memset(&csrow->kobj, 0, sizeof(csrow->kobj)); | ||
546 | |||
547 | /* generate ..../edac/mc/mc<id>/csrow<index> */ | ||
548 | |||
549 | csrow->kobj.parent = edac_mci_kobj; | ||
550 | csrow->kobj.ktype = &ktype_csrow; | ||
551 | |||
552 | /* name this instance of csrow<id> */ | ||
553 | err = kobject_set_name(&csrow->kobj,"csrow%d",index); | ||
554 | if (err) | ||
555 | goto error_exit; | ||
556 | |||
557 | /* Instanstiate the csrow object */ | ||
558 | err = kobject_register(&csrow->kobj); | ||
559 | if (!err) { | ||
560 | /* Create the dyanmic attribute files on this csrow, | ||
561 | * namely, the DIMM labels and the channel ce_count | ||
562 | */ | ||
563 | for (chan = 0; chan < csrow->nr_channels; chan++) { | ||
564 | err = edac_create_channel_files(&csrow->kobj,chan); | ||
565 | if (err) | ||
566 | break; | ||
567 | } | ||
568 | } | ||
569 | |||
570 | error_exit: | ||
571 | return err; | ||
572 | } | ||
573 | |||
574 | /* default sysfs methods and data structures for the main MCI kobject */ | ||
575 | |||
576 | static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, | ||
577 | const char *data, size_t count) | ||
578 | { | ||
579 | int row, chan; | ||
580 | |||
581 | mci->ue_noinfo_count = 0; | ||
582 | mci->ce_noinfo_count = 0; | ||
583 | mci->ue_count = 0; | ||
584 | mci->ce_count = 0; | ||
585 | |||
586 | for (row = 0; row < mci->nr_csrows; row++) { | ||
587 | struct csrow_info *ri = &mci->csrows[row]; | ||
588 | |||
589 | ri->ue_count = 0; | ||
590 | ri->ce_count = 0; | ||
591 | |||
592 | for (chan = 0; chan < ri->nr_channels; chan++) | ||
593 | ri->channels[chan].ce_count = 0; | ||
594 | } | ||
595 | |||
596 | mci->start_time = jiffies; | ||
597 | return count; | ||
598 | } | ||
599 | |||
600 | /* memory scrubbing */ | ||
601 | static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, | ||
602 | const char *data, size_t count) | ||
603 | { | ||
604 | u32 bandwidth = -1; | ||
605 | |||
606 | if (mci->set_sdram_scrub_rate) { | ||
607 | |||
608 | memctrl_int_store(&bandwidth, data, count); | ||
609 | |||
610 | if (!(*mci->set_sdram_scrub_rate)(mci, &bandwidth)) { | ||
611 | edac_printk(KERN_DEBUG, EDAC_MC, | ||
612 | "Scrub rate set successfully, applied: %d\n", | ||
613 | bandwidth); | ||
614 | } else { | ||
615 | /* FIXME: error codes maybe? */ | ||
616 | edac_printk(KERN_DEBUG, EDAC_MC, | ||
617 | "Scrub rate set FAILED, could not apply: %d\n", | ||
618 | bandwidth); | ||
619 | } | ||
620 | } else { | ||
621 | /* FIXME: produce "not implemented" ERROR for user-side. */ | ||
622 | edac_printk(KERN_WARNING, EDAC_MC, | ||
623 | "Memory scrubbing 'set'control is not implemented!\n"); | ||
624 | } | ||
625 | return count; | ||
626 | } | ||
627 | |||
628 | static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) | ||
629 | { | ||
630 | u32 bandwidth = -1; | ||
631 | |||
632 | if (mci->get_sdram_scrub_rate) { | ||
633 | if (!(*mci->get_sdram_scrub_rate)(mci, &bandwidth)) { | ||
634 | edac_printk(KERN_DEBUG, EDAC_MC, | ||
635 | "Scrub rate successfully, fetched: %d\n", | ||
636 | bandwidth); | ||
637 | } else { | ||
638 | /* FIXME: error codes maybe? */ | ||
639 | edac_printk(KERN_DEBUG, EDAC_MC, | ||
640 | "Scrub rate fetch FAILED, got: %d\n", | ||
641 | bandwidth); | ||
642 | } | ||
643 | } else { | ||
644 | /* FIXME: produce "not implemented" ERROR for user-side. */ | ||
645 | edac_printk(KERN_WARNING, EDAC_MC, | ||
646 | "Memory scrubbing 'get' control is not implemented!\n"); | ||
647 | } | ||
648 | return sprintf(data, "%d\n", bandwidth); | ||
649 | } | ||
650 | |||
651 | /* default attribute files for the MCI object */ | ||
652 | static ssize_t mci_ue_count_show(struct mem_ctl_info *mci, char *data) | ||
653 | { | ||
654 | return sprintf(data,"%d\n", mci->ue_count); | ||
655 | } | ||
656 | |||
657 | static ssize_t mci_ce_count_show(struct mem_ctl_info *mci, char *data) | ||
658 | { | ||
659 | return sprintf(data,"%d\n", mci->ce_count); | ||
660 | } | ||
661 | |||
662 | static ssize_t mci_ce_noinfo_show(struct mem_ctl_info *mci, char *data) | ||
663 | { | ||
664 | return sprintf(data,"%d\n", mci->ce_noinfo_count); | ||
665 | } | ||
666 | |||
667 | static ssize_t mci_ue_noinfo_show(struct mem_ctl_info *mci, char *data) | ||
668 | { | ||
669 | return sprintf(data,"%d\n", mci->ue_noinfo_count); | ||
670 | } | ||
671 | |||
672 | static ssize_t mci_seconds_show(struct mem_ctl_info *mci, char *data) | ||
673 | { | ||
674 | return sprintf(data,"%ld\n", (jiffies - mci->start_time) / HZ); | ||
675 | } | ||
676 | |||
677 | static ssize_t mci_ctl_name_show(struct mem_ctl_info *mci, char *data) | ||
678 | { | ||
679 | return sprintf(data,"%s\n", mci->ctl_name); | ||
680 | } | ||
681 | |||
682 | static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) | ||
683 | { | ||
684 | int total_pages, csrow_idx; | ||
685 | |||
686 | for (total_pages = csrow_idx = 0; csrow_idx < mci->nr_csrows; | ||
687 | csrow_idx++) { | ||
688 | struct csrow_info *csrow = &mci->csrows[csrow_idx]; | ||
689 | |||
690 | if (!csrow->nr_pages) | ||
691 | continue; | ||
692 | |||
693 | total_pages += csrow->nr_pages; | ||
694 | } | ||
695 | |||
696 | return sprintf(data,"%u\n", PAGES_TO_MiB(total_pages)); | ||
697 | } | ||
698 | |||
699 | struct mcidev_attribute { | ||
700 | struct attribute attr; | ||
701 | ssize_t (*show)(struct mem_ctl_info *,char *); | ||
702 | ssize_t (*store)(struct mem_ctl_info *, const char *,size_t); | ||
703 | }; | ||
704 | |||
705 | #define to_mci(k) container_of(k, struct mem_ctl_info, edac_mci_kobj) | ||
706 | #define to_mcidev_attr(a) container_of(a, struct mcidev_attribute, attr) | ||
707 | |||
708 | /* MCI show/store functions for top most object */ | ||
709 | static ssize_t mcidev_show(struct kobject *kobj, struct attribute *attr, | ||
710 | char *buffer) | ||
711 | { | ||
712 | struct mem_ctl_info *mem_ctl_info = to_mci(kobj); | ||
713 | struct mcidev_attribute * mcidev_attr = to_mcidev_attr(attr); | ||
714 | |||
715 | if (mcidev_attr->show) | ||
716 | return mcidev_attr->show(mem_ctl_info, buffer); | ||
717 | |||
718 | return -EIO; | ||
719 | } | ||
720 | |||
721 | static ssize_t mcidev_store(struct kobject *kobj, struct attribute *attr, | ||
722 | const char *buffer, size_t count) | ||
723 | { | ||
724 | struct mem_ctl_info *mem_ctl_info = to_mci(kobj); | ||
725 | struct mcidev_attribute * mcidev_attr = to_mcidev_attr(attr); | ||
726 | |||
727 | if (mcidev_attr->store) | ||
728 | return mcidev_attr->store(mem_ctl_info, buffer, count); | ||
729 | |||
730 | return -EIO; | ||
731 | } | ||
732 | |||
733 | static struct sysfs_ops mci_ops = { | ||
734 | .show = mcidev_show, | ||
735 | .store = mcidev_store | ||
736 | }; | ||
737 | |||
738 | #define MCIDEV_ATTR(_name,_mode,_show,_store) \ | ||
739 | static struct mcidev_attribute mci_attr_##_name = { \ | ||
740 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | ||
741 | .show = _show, \ | ||
742 | .store = _store, \ | ||
743 | }; | ||
744 | |||
745 | /* default Control file */ | ||
746 | MCIDEV_ATTR(reset_counters,S_IWUSR,NULL,mci_reset_counters_store); | ||
747 | |||
748 | /* default Attribute files */ | ||
749 | MCIDEV_ATTR(mc_name,S_IRUGO,mci_ctl_name_show,NULL); | ||
750 | MCIDEV_ATTR(size_mb,S_IRUGO,mci_size_mb_show,NULL); | ||
751 | MCIDEV_ATTR(seconds_since_reset,S_IRUGO,mci_seconds_show,NULL); | ||
752 | MCIDEV_ATTR(ue_noinfo_count,S_IRUGO,mci_ue_noinfo_show,NULL); | ||
753 | MCIDEV_ATTR(ce_noinfo_count,S_IRUGO,mci_ce_noinfo_show,NULL); | ||
754 | MCIDEV_ATTR(ue_count,S_IRUGO,mci_ue_count_show,NULL); | ||
755 | MCIDEV_ATTR(ce_count,S_IRUGO,mci_ce_count_show,NULL); | ||
756 | |||
757 | /* memory scrubber attribute file */ | ||
758 | MCIDEV_ATTR(sdram_scrub_rate,S_IRUGO|S_IWUSR,mci_sdram_scrub_rate_show,mci_sdram_scrub_rate_store); | ||
759 | |||
760 | static struct mcidev_attribute *mci_attr[] = { | ||
761 | &mci_attr_reset_counters, | ||
762 | &mci_attr_mc_name, | ||
763 | &mci_attr_size_mb, | ||
764 | &mci_attr_seconds_since_reset, | ||
765 | &mci_attr_ue_noinfo_count, | ||
766 | &mci_attr_ce_noinfo_count, | ||
767 | &mci_attr_ue_count, | ||
768 | &mci_attr_ce_count, | ||
769 | &mci_attr_sdram_scrub_rate, | ||
770 | NULL | ||
771 | }; | ||
772 | |||
773 | /* | ||
774 | * Release of a MC controlling instance | ||
775 | */ | ||
776 | static void edac_mci_instance_release(struct kobject *kobj) | ||
777 | { | ||
778 | struct mem_ctl_info *mci; | ||
779 | |||
780 | mci = to_mci(kobj); | ||
781 | debugf0("%s() idx=%d\n", __func__, mci->mc_idx); | ||
782 | complete(&mci->kobj_complete); | ||
783 | } | ||
784 | |||
785 | static struct kobj_type ktype_mci = { | ||
786 | .release = edac_mci_instance_release, | ||
787 | .sysfs_ops = &mci_ops, | ||
788 | .default_attrs = (struct attribute **) mci_attr, | ||
789 | }; | ||
790 | |||
791 | |||
792 | #define EDAC_DEVICE_SYMLINK "device" | ||
793 | |||
794 | /* | ||
795 | * Create a new Memory Controller kobject instance, | ||
796 | * mc<id> under the 'mc' directory | ||
797 | * | ||
798 | * Return: | ||
799 | * 0 Success | ||
800 | * !0 Failure | ||
801 | */ | ||
802 | int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) | ||
803 | { | ||
804 | int i; | ||
805 | int err; | ||
806 | struct csrow_info *csrow; | ||
807 | struct kobject *edac_mci_kobj=&mci->edac_mci_kobj; | ||
808 | |||
809 | debugf0("%s() idx=%d\n", __func__, mci->mc_idx); | ||
810 | memset(edac_mci_kobj, 0, sizeof(*edac_mci_kobj)); | ||
811 | |||
812 | /* set the name of the mc<id> object */ | ||
813 | err = kobject_set_name(edac_mci_kobj,"mc%d",mci->mc_idx); | ||
814 | if (err) | ||
815 | return err; | ||
816 | |||
817 | /* link to our parent the '..../edac/mc' object */ | ||
818 | edac_mci_kobj->parent = &edac_memctrl_kobj; | ||
819 | edac_mci_kobj->ktype = &ktype_mci; | ||
820 | |||
821 | /* register the mc<id> kobject */ | ||
822 | err = kobject_register(edac_mci_kobj); | ||
823 | if (err) | ||
824 | return err; | ||
825 | |||
826 | /* create a symlink for the device */ | ||
827 | err = sysfs_create_link(edac_mci_kobj, &mci->dev->kobj, | ||
828 | EDAC_DEVICE_SYMLINK); | ||
829 | if (err) | ||
830 | goto fail0; | ||
831 | |||
832 | /* Make directories for each CSROW object | ||
833 | * under the mc<id> kobject | ||
834 | */ | ||
835 | for (i = 0; i < mci->nr_csrows; i++) { | ||
836 | csrow = &mci->csrows[i]; | ||
837 | |||
838 | /* Only expose populated CSROWs */ | ||
839 | if (csrow->nr_pages > 0) { | ||
840 | err = edac_create_csrow_object(edac_mci_kobj,csrow,i); | ||
841 | if (err) | ||
842 | goto fail1; | ||
843 | } | ||
844 | } | ||
845 | |||
846 | return 0; | ||
847 | |||
848 | /* CSROW error: backout what has already been registered, */ | ||
849 | fail1: | ||
850 | for ( i--; i >= 0; i--) { | ||
851 | if (csrow->nr_pages > 0) { | ||
852 | init_completion(&csrow->kobj_complete); | ||
853 | kobject_unregister(&mci->csrows[i].kobj); | ||
854 | wait_for_completion(&csrow->kobj_complete); | ||
855 | } | ||
856 | } | ||
857 | |||
858 | fail0: | ||
859 | init_completion(&mci->kobj_complete); | ||
860 | kobject_unregister(edac_mci_kobj); | ||
861 | wait_for_completion(&mci->kobj_complete); | ||
862 | return err; | ||
863 | } | ||
864 | |||
865 | /* | ||
866 | * remove a Memory Controller instance | ||
867 | */ | ||
868 | void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) | ||
869 | { | ||
870 | int i; | ||
871 | |||
872 | debugf0("%s()\n", __func__); | ||
873 | |||
874 | /* remove all csrow kobjects */ | ||
875 | for (i = 0; i < mci->nr_csrows; i++) { | ||
876 | if (mci->csrows[i].nr_pages > 0) { | ||
877 | init_completion(&mci->csrows[i].kobj_complete); | ||
878 | kobject_unregister(&mci->csrows[i].kobj); | ||
879 | wait_for_completion(&mci->csrows[i].kobj_complete); | ||
880 | } | ||
881 | } | ||
882 | |||
883 | sysfs_remove_link(&mci->edac_mci_kobj, EDAC_DEVICE_SYMLINK); | ||
884 | init_completion(&mci->kobj_complete); | ||
885 | kobject_unregister(&mci->edac_mci_kobj); | ||
886 | wait_for_completion(&mci->kobj_complete); | ||
887 | } | ||
888 | |||
889 | |||