blob: 7002c9cab9998cb183f4724c182b37e9cf50396f [file] [log] [blame]
Douglas Thompson7c9281d2007-07-19 01:49:33 -07001/*
2 * edac_mc kernel module
Douglas Thompson42a8e392007-07-19 01:50:10 -07003 * (C) 2005-2007 Linux Networx (http://lnxi.com)
4 *
Douglas Thompson7c9281d2007-07-19 01:49:33 -07005 * This file may be distributed under the terms of the
6 * GNU General Public License.
7 *
Douglas Thompson42a8e392007-07-19 01:50:10 -07008 * Written Doug Thompson <norsk5@xmission.com> www.softwarebitmaker.com
Douglas Thompson7c9281d2007-07-19 01:49:33 -07009 *
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -030010 * (c) 2012 - Mauro Carvalho Chehab <mchehab@redhat.com>
11 * The entire API were re-written, and ported to use struct device
12 *
Douglas Thompson7c9281d2007-07-19 01:49:33 -070013 */
14
Douglas Thompson7c9281d2007-07-19 01:49:33 -070015#include <linux/ctype.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090016#include <linux/slab.h>
Borislav Petkov30e1f7a2010-09-02 17:26:48 +020017#include <linux/edac.h>
Doug Thompson8096cfa2007-07-19 01:50:27 -070018#include <linux/bug.h>
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -030019#include <linux/pm_runtime.h>
Douglas Thompson7c9281d2007-07-19 01:49:33 -070020
Douglas Thompson20bcb7a2007-07-19 01:49:47 -070021#include "edac_core.h"
Douglas Thompson7c9281d2007-07-19 01:49:33 -070022#include "edac_module.h"
23
24/* MC EDAC Controls, setable by module parameter, and sysfs */
Dave Jiang4de78c62007-07-19 01:49:54 -070025static int edac_mc_log_ue = 1;
26static int edac_mc_log_ce = 1;
Douglas Thompsonf0440912007-07-19 01:50:19 -070027static int edac_mc_panic_on_ue;
Dave Jiang4de78c62007-07-19 01:49:54 -070028static int edac_mc_poll_msec = 1000;
Douglas Thompson7c9281d2007-07-19 01:49:33 -070029
30/* Getter functions for above */
Dave Jiang4de78c62007-07-19 01:49:54 -070031int edac_mc_get_log_ue(void)
Douglas Thompson7c9281d2007-07-19 01:49:33 -070032{
Dave Jiang4de78c62007-07-19 01:49:54 -070033 return edac_mc_log_ue;
Douglas Thompson7c9281d2007-07-19 01:49:33 -070034}
35
Dave Jiang4de78c62007-07-19 01:49:54 -070036int edac_mc_get_log_ce(void)
Douglas Thompson7c9281d2007-07-19 01:49:33 -070037{
Dave Jiang4de78c62007-07-19 01:49:54 -070038 return edac_mc_log_ce;
Douglas Thompson7c9281d2007-07-19 01:49:33 -070039}
40
Dave Jiang4de78c62007-07-19 01:49:54 -070041int edac_mc_get_panic_on_ue(void)
Douglas Thompson7c9281d2007-07-19 01:49:33 -070042{
Dave Jiang4de78c62007-07-19 01:49:54 -070043 return edac_mc_panic_on_ue;
Douglas Thompson7c9281d2007-07-19 01:49:33 -070044}
45
Dave Jiang81d87cb2007-07-19 01:49:52 -070046/* this is temporary */
47int edac_mc_get_poll_msec(void)
48{
Dave Jiang4de78c62007-07-19 01:49:54 -070049 return edac_mc_poll_msec;
Douglas Thompson7c9281d2007-07-19 01:49:33 -070050}
51
Arthur Jones096846e2008-07-25 01:49:09 -070052static int edac_set_poll_msec(const char *val, struct kernel_param *kp)
53{
54 long l;
55 int ret;
56
57 if (!val)
58 return -EINVAL;
59
60 ret = strict_strtol(val, 0, &l);
61 if (ret == -EINVAL || ((int)l != l))
62 return -EINVAL;
63 *((int *)kp->arg) = l;
64
65 /* notify edac_mc engine to reset the poll period */
66 edac_mc_reset_delay_period(l);
67
68 return 0;
69}
70
Douglas Thompson7c9281d2007-07-19 01:49:33 -070071/* Parameter declarations for above */
Dave Jiang4de78c62007-07-19 01:49:54 -070072module_param(edac_mc_panic_on_ue, int, 0644);
73MODULE_PARM_DESC(edac_mc_panic_on_ue, "Panic on uncorrected error: 0=off 1=on");
74module_param(edac_mc_log_ue, int, 0644);
75MODULE_PARM_DESC(edac_mc_log_ue,
Douglas Thompson079708b2007-07-19 01:49:58 -070076 "Log uncorrectable error to console: 0=off 1=on");
Dave Jiang4de78c62007-07-19 01:49:54 -070077module_param(edac_mc_log_ce, int, 0644);
78MODULE_PARM_DESC(edac_mc_log_ce,
Douglas Thompson079708b2007-07-19 01:49:58 -070079 "Log correctable error to console: 0=off 1=on");
Arthur Jones096846e2008-07-25 01:49:09 -070080module_param_call(edac_mc_poll_msec, edac_set_poll_msec, param_get_int,
81 &edac_mc_poll_msec, 0644);
Dave Jiang4de78c62007-07-19 01:49:54 -070082MODULE_PARM_DESC(edac_mc_poll_msec, "Polling period in milliseconds");
Douglas Thompson7c9281d2007-07-19 01:49:33 -070083
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -030084static struct device mci_pdev;
85
Douglas Thompson7c9281d2007-07-19 01:49:33 -070086/*
87 * various constants for Memory Controllers
88 */
89static const char *mem_types[] = {
90 [MEM_EMPTY] = "Empty",
91 [MEM_RESERVED] = "Reserved",
92 [MEM_UNKNOWN] = "Unknown",
93 [MEM_FPM] = "FPM",
94 [MEM_EDO] = "EDO",
95 [MEM_BEDO] = "BEDO",
96 [MEM_SDR] = "Unbuffered-SDR",
97 [MEM_RDR] = "Registered-SDR",
98 [MEM_DDR] = "Unbuffered-DDR",
99 [MEM_RDDR] = "Registered-DDR",
Dave Jiang1a9b85e2007-07-19 01:49:38 -0700100 [MEM_RMBS] = "RMBS",
101 [MEM_DDR2] = "Unbuffered-DDR2",
102 [MEM_FB_DDR2] = "FullyBuffered-DDR2",
Benjamin Herrenschmidt1d5f7262008-02-07 00:14:52 -0800103 [MEM_RDDR2] = "Registered-DDR2",
Yang Shib1cfebc2009-06-30 11:41:22 -0700104 [MEM_XDR] = "XDR",
105 [MEM_DDR3] = "Unbuffered-DDR3",
106 [MEM_RDDR3] = "Registered-DDR3"
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700107};
108
109static const char *dev_types[] = {
110 [DEV_UNKNOWN] = "Unknown",
111 [DEV_X1] = "x1",
112 [DEV_X2] = "x2",
113 [DEV_X4] = "x4",
114 [DEV_X8] = "x8",
115 [DEV_X16] = "x16",
116 [DEV_X32] = "x32",
117 [DEV_X64] = "x64"
118};
119
120static const char *edac_caps[] = {
121 [EDAC_UNKNOWN] = "Unknown",
122 [EDAC_NONE] = "None",
123 [EDAC_RESERVED] = "Reserved",
124 [EDAC_PARITY] = "PARITY",
125 [EDAC_EC] = "EC",
126 [EDAC_SECDED] = "SECDED",
127 [EDAC_S2ECD2ED] = "S2ECD2ED",
128 [EDAC_S4ECD4ED] = "S4ECD4ED",
129 [EDAC_S8ECD8ED] = "S8ECD8ED",
130 [EDAC_S16ECD16ED] = "S16ECD16ED"
131};
132
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300133/*
134 * EDAC sysfs CSROW data structures and methods
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700135 */
136
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300137#define to_csrow(k) container_of(k, struct csrow_info, dev)
138
139/*
140 * We need it to avoid namespace conflicts between the legacy API
141 * and the per-dimm/per-rank one
142 */
143#define DEVICE_ATTR_LEGACY(_name, _mode, _show, _store) \
144 struct device_attribute dev_attr_legacy_##_name = __ATTR(_name, _mode, _show, _store)
145
146struct dev_ch_attribute {
147 struct device_attribute attr;
148 int channel;
149};
150
151#define DEVICE_CHANNEL(_name, _mode, _show, _store, _var) \
152 struct dev_ch_attribute dev_attr_legacy_##_name = \
153 { __ATTR(_name, _mode, _show, _store), (_var) }
154
155#define to_channel(k) (container_of(k, struct dev_ch_attribute, attr)->channel)
156
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700157/* Set of more default csrow<id> attribute show/store functions */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300158static ssize_t csrow_ue_count_show(struct device *dev,
159 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700160{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300161 struct csrow_info *csrow = to_csrow(dev);
162
Douglas Thompson079708b2007-07-19 01:49:58 -0700163 return sprintf(data, "%u\n", csrow->ue_count);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700164}
165
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300166static ssize_t csrow_ce_count_show(struct device *dev,
167 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700168{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300169 struct csrow_info *csrow = to_csrow(dev);
170
Douglas Thompson079708b2007-07-19 01:49:58 -0700171 return sprintf(data, "%u\n", csrow->ce_count);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700172}
173
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300174static ssize_t csrow_size_show(struct device *dev,
175 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700176{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300177 struct csrow_info *csrow = to_csrow(dev);
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300178 int i;
179 u32 nr_pages = 0;
180
181 for (i = 0; i < csrow->nr_channels; i++)
182 nr_pages += csrow->channels[i].dimm->nr_pages;
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300183 return sprintf(data, "%u\n", PAGES_TO_MiB(nr_pages));
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700184}
185
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300186static ssize_t csrow_mem_type_show(struct device *dev,
187 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700188{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300189 struct csrow_info *csrow = to_csrow(dev);
190
Mauro Carvalho Chehab084a4fc2012-01-27 18:38:08 -0300191 return sprintf(data, "%s\n", mem_types[csrow->channels[0].dimm->mtype]);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700192}
193
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300194static ssize_t csrow_dev_type_show(struct device *dev,
195 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700196{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300197 struct csrow_info *csrow = to_csrow(dev);
198
Mauro Carvalho Chehab084a4fc2012-01-27 18:38:08 -0300199 return sprintf(data, "%s\n", dev_types[csrow->channels[0].dimm->dtype]);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700200}
201
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300202static ssize_t csrow_edac_mode_show(struct device *dev,
203 struct device_attribute *mattr,
204 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700205{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300206 struct csrow_info *csrow = to_csrow(dev);
207
Mauro Carvalho Chehab084a4fc2012-01-27 18:38:08 -0300208 return sprintf(data, "%s\n", edac_caps[csrow->channels[0].dimm->edac_mode]);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700209}
210
211/* show/store functions for DIMM Label attributes */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300212static ssize_t channel_dimm_label_show(struct device *dev,
213 struct device_attribute *mattr,
214 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700215{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300216 struct csrow_info *csrow = to_csrow(dev);
217 unsigned chan = to_channel(mattr);
218 struct rank_info *rank = &csrow->channels[chan];
219
Arthur Jones124682c2008-07-25 01:49:12 -0700220 /* if field has not been initialized, there is nothing to send */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300221 if (!rank->dimm->label[0])
Arthur Jones124682c2008-07-25 01:49:12 -0700222 return 0;
223
224 return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n",
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300225 rank->dimm->label);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700226}
227
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300228static ssize_t channel_dimm_label_store(struct device *dev,
229 struct device_attribute *mattr,
230 const char *data, size_t count)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700231{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300232 struct csrow_info *csrow = to_csrow(dev);
233 unsigned chan = to_channel(mattr);
234 struct rank_info *rank = &csrow->channels[chan];
235
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700236 ssize_t max_size = 0;
237
Douglas Thompson079708b2007-07-19 01:49:58 -0700238 max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1);
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300239 strncpy(rank->dimm->label, data, max_size);
240 rank->dimm->label[max_size] = '\0';
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700241
242 return max_size;
243}
244
245/* show function for dynamic chX_ce_count attribute */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300246static ssize_t channel_ce_count_show(struct device *dev,
247 struct device_attribute *mattr, char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700248{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300249 struct csrow_info *csrow = to_csrow(dev);
250 unsigned chan = to_channel(mattr);
251 struct rank_info *rank = &csrow->channels[chan];
252
253 return sprintf(data, "%u\n", rank->ce_count);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700254}
255
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300256/* cwrow<id>/attribute files */
257DEVICE_ATTR_LEGACY(size_mb, S_IRUGO, csrow_size_show, NULL);
258DEVICE_ATTR_LEGACY(dev_type, S_IRUGO, csrow_dev_type_show, NULL);
259DEVICE_ATTR_LEGACY(mem_type, S_IRUGO, csrow_mem_type_show, NULL);
260DEVICE_ATTR_LEGACY(edac_mode, S_IRUGO, csrow_edac_mode_show, NULL);
261DEVICE_ATTR_LEGACY(ue_count, S_IRUGO, csrow_ue_count_show, NULL);
262DEVICE_ATTR_LEGACY(ce_count, S_IRUGO, csrow_ce_count_show, NULL);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700263
264/* default attributes of the CSROW<id> object */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300265static struct attribute *csrow_attrs[] = {
266 &dev_attr_legacy_dev_type.attr,
267 &dev_attr_legacy_mem_type.attr,
268 &dev_attr_legacy_edac_mode.attr,
269 &dev_attr_legacy_size_mb.attr,
270 &dev_attr_legacy_ue_count.attr,
271 &dev_attr_legacy_ce_count.attr,
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700272 NULL,
273};
274
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300275static struct attribute_group csrow_attr_grp = {
276 .attrs = csrow_attrs,
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700277};
278
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300279static const struct attribute_group *csrow_attr_groups[] = {
280 &csrow_attr_grp,
281 NULL
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700282};
283
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300284static void csrow_attr_release(struct device *device)
285{
286 debugf1("Releasing csrow device %s\n", dev_name(device));
287}
288
289static struct device_type csrow_attr_type = {
290 .groups = csrow_attr_groups,
291 .release = csrow_attr_release,
292};
293
294/*
295 * possible dynamic channel DIMM Label attribute files
296 *
297 */
298
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700299#define EDAC_NR_CHANNELS 6
300
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300301DEVICE_CHANNEL(ch0_dimm_label, S_IRUGO | S_IWUSR,
302 channel_dimm_label_show, channel_dimm_label_store, 0);
303DEVICE_CHANNEL(ch1_dimm_label, S_IRUGO | S_IWUSR,
304 channel_dimm_label_show, channel_dimm_label_store, 1);
305DEVICE_CHANNEL(ch2_dimm_label, S_IRUGO | S_IWUSR,
306 channel_dimm_label_show, channel_dimm_label_store, 2);
307DEVICE_CHANNEL(ch3_dimm_label, S_IRUGO | S_IWUSR,
308 channel_dimm_label_show, channel_dimm_label_store, 3);
309DEVICE_CHANNEL(ch4_dimm_label, S_IRUGO | S_IWUSR,
310 channel_dimm_label_show, channel_dimm_label_store, 4);
311DEVICE_CHANNEL(ch5_dimm_label, S_IRUGO | S_IWUSR,
312 channel_dimm_label_show, channel_dimm_label_store, 5);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700313
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300314/* Total possible dynamic DIMM Label attribute file table */
315static struct device_attribute *dynamic_csrow_dimm_attr[] = {
316 &dev_attr_legacy_ch0_dimm_label.attr,
317 &dev_attr_legacy_ch1_dimm_label.attr,
318 &dev_attr_legacy_ch2_dimm_label.attr,
319 &dev_attr_legacy_ch3_dimm_label.attr,
320 &dev_attr_legacy_ch4_dimm_label.attr,
321 &dev_attr_legacy_ch5_dimm_label.attr
322};
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700323
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300324/* possible dynamic channel ce_count attribute files */
325DEVICE_CHANNEL(ch0_ce_count, S_IRUGO | S_IWUSR,
326 channel_ce_count_show, NULL, 0);
327DEVICE_CHANNEL(ch1_ce_count, S_IRUGO | S_IWUSR,
328 channel_ce_count_show, NULL, 1);
329DEVICE_CHANNEL(ch2_ce_count, S_IRUGO | S_IWUSR,
330 channel_ce_count_show, NULL, 2);
331DEVICE_CHANNEL(ch3_ce_count, S_IRUGO | S_IWUSR,
332 channel_ce_count_show, NULL, 3);
333DEVICE_CHANNEL(ch4_ce_count, S_IRUGO | S_IWUSR,
334 channel_ce_count_show, NULL, 4);
335DEVICE_CHANNEL(ch5_ce_count, S_IRUGO | S_IWUSR,
336 channel_ce_count_show, NULL, 5);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700337
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300338/* Total possible dynamic ce_count attribute file table */
339static struct device_attribute *dynamic_csrow_ce_count_attr[] = {
340 &dev_attr_legacy_ch0_ce_count.attr,
341 &dev_attr_legacy_ch1_ce_count.attr,
342 &dev_attr_legacy_ch2_ce_count.attr,
343 &dev_attr_legacy_ch3_ce_count.attr,
344 &dev_attr_legacy_ch4_ce_count.attr,
345 &dev_attr_legacy_ch5_ce_count.attr
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700346};
347
348/* Create a CSROW object under specifed edac_mc_device */
Doug Thompson8096cfa2007-07-19 01:50:27 -0700349static int edac_create_csrow_object(struct mem_ctl_info *mci,
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300350 struct csrow_info *csrow, int index)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700351{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300352 int err, chan;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700353
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300354 if (csrow->nr_channels >= EDAC_NR_CHANNELS)
355 return -ENODEV;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700356
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300357 csrow->dev.type = &csrow_attr_type;
358 csrow->dev.bus = &mci->bus;
359 device_initialize(&csrow->dev);
360 csrow->dev.parent = &mci->dev;
361 dev_set_name(&csrow->dev, "csrow%d", index);
362 dev_set_drvdata(&csrow->dev, csrow);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700363
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300364 debugf0("%s(): creating (virtual) csrow node %s\n", __func__,
365 dev_name(&csrow->dev));
Doug Thompson8096cfa2007-07-19 01:50:27 -0700366
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300367 err = device_add(&csrow->dev);
368 if (err < 0)
369 return err;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700370
Doug Thompson8096cfa2007-07-19 01:50:27 -0700371 for (chan = 0; chan < csrow->nr_channels; chan++) {
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300372 err = device_create_file(&csrow->dev,
373 dynamic_csrow_dimm_attr[chan]);
374 if (err < 0)
375 goto error;
376 err = device_create_file(&csrow->dev,
377 dynamic_csrow_ce_count_attr[chan]);
378 if (err < 0) {
379 device_remove_file(&csrow->dev,
380 dynamic_csrow_dimm_attr[chan]);
381 goto error;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700382 }
383 }
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300384
Doug Thompson8096cfa2007-07-19 01:50:27 -0700385 return 0;
386
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300387error:
388 for (--chan; chan >= 0; chan--) {
389 device_remove_file(&csrow->dev,
390 dynamic_csrow_dimm_attr[chan]);
391 device_remove_file(&csrow->dev,
392 dynamic_csrow_ce_count_attr[chan]);
393 }
394 put_device(&csrow->dev);
Doug Thompson8096cfa2007-07-19 01:50:27 -0700395
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700396 return err;
397}
398
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300399/* Create a CSROW object under specifed edac_mc_device */
400static int edac_create_csrow_objects(struct mem_ctl_info *mci)
401{
402 int err, i, chan;
403 struct csrow_info *csrow;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700404
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300405 for (i = 0; i < mci->nr_csrows; i++) {
406 err = edac_create_csrow_object(mci, &mci->csrows[i], i);
407 if (err < 0)
408 goto error;
409 }
410 return 0;
411
412error:
413 for (--i; i >= 0; i--) {
414 csrow = &mci->csrows[i];
415 for (chan = csrow->nr_channels - 1; chan >= 0; chan--) {
416 device_remove_file(&csrow->dev,
417 dynamic_csrow_dimm_attr[chan]);
418 device_remove_file(&csrow->dev,
419 dynamic_csrow_ce_count_attr[chan]);
420 }
421 put_device(&mci->csrows[i].dev);
422 }
423
424 return err;
425}
426
427static void edac_delete_csrow_objects(struct mem_ctl_info *mci)
428{
429 int i, chan;
430 struct csrow_info *csrow;
431
432 for (i = mci->nr_csrows - 1; i >= 0; i--) {
433 csrow = &mci->csrows[i];
434 for (chan = csrow->nr_channels - 1; chan >= 0; chan--) {
435 debugf1("Removing csrow %d channel %d sysfs nodes\n",
436 i, chan);
437 device_remove_file(&csrow->dev,
438 dynamic_csrow_dimm_attr[chan]);
439 device_remove_file(&csrow->dev,
440 dynamic_csrow_ce_count_attr[chan]);
441 }
442 put_device(&mci->csrows[i].dev);
443 device_del(&mci->csrows[i].dev);
444 }
445}
446
447/*
448 * Memory controller device
449 */
450
451#define to_mci(k) container_of(k, struct mem_ctl_info, dev)
452
453static ssize_t mci_reset_counters_store(struct device *dev,
454 struct device_attribute *mattr,
Douglas Thompson079708b2007-07-19 01:49:58 -0700455 const char *data, size_t count)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700456{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300457 struct mem_ctl_info *mci = to_mci(dev);
458 int cnt, row, chan, i;
Mauro Carvalho Chehab5926ff52012-02-09 11:05:20 -0300459 mci->ue_mc = 0;
460 mci->ce_mc = 0;
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300461 mci->ue_noinfo_count = 0;
462 mci->ce_noinfo_count = 0;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700463
464 for (row = 0; row < mci->nr_csrows; row++) {
465 struct csrow_info *ri = &mci->csrows[row];
466
467 ri->ue_count = 0;
468 ri->ce_count = 0;
469
470 for (chan = 0; chan < ri->nr_channels; chan++)
471 ri->channels[chan].ce_count = 0;
472 }
473
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300474 cnt = 1;
475 for (i = 0; i < mci->n_layers; i++) {
476 cnt *= mci->layers[i].size;
477 memset(mci->ce_per_layer[i], 0, cnt * sizeof(u32));
478 memset(mci->ue_per_layer[i], 0, cnt * sizeof(u32));
479 }
480
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700481 mci->start_time = jiffies;
482 return count;
483}
484
Borislav Petkov39094442010-11-24 19:52:09 +0100485/* Memory scrubbing interface:
486 *
487 * A MC driver can limit the scrubbing bandwidth based on the CPU type.
488 * Therefore, ->set_sdram_scrub_rate should be made to return the actual
489 * bandwidth that is accepted or 0 when scrubbing is to be disabled.
490 *
491 * Negative value still means that an error has occurred while setting
492 * the scrub rate.
493 */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300494static ssize_t mci_sdram_scrub_rate_store(struct device *dev,
495 struct device_attribute *mattr,
Borislav Petkoveba042a2010-05-25 18:21:07 +0200496 const char *data, size_t count)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700497{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300498 struct mem_ctl_info *mci = to_mci(dev);
Borislav Petkoveba042a2010-05-25 18:21:07 +0200499 unsigned long bandwidth = 0;
Borislav Petkov39094442010-11-24 19:52:09 +0100500 int new_bw = 0;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700501
Borislav Petkov39094442010-11-24 19:52:09 +0100502 if (!mci->set_sdram_scrub_rate)
Borislav Petkov5e8e19b2011-09-21 14:10:43 +0200503 return -ENODEV;
Borislav Petkoveba042a2010-05-25 18:21:07 +0200504
505 if (strict_strtoul(data, 10, &bandwidth) < 0)
506 return -EINVAL;
507
Borislav Petkov39094442010-11-24 19:52:09 +0100508 new_bw = mci->set_sdram_scrub_rate(mci, bandwidth);
Markus Trippelsdorf49496032011-04-20 14:28:45 -0400509 if (new_bw < 0) {
510 edac_printk(KERN_WARNING, EDAC_MC,
511 "Error setting scrub rate to: %lu\n", bandwidth);
512 return -EINVAL;
Borislav Petkoveba042a2010-05-25 18:21:07 +0200513 }
Borislav Petkov39094442010-11-24 19:52:09 +0100514
Markus Trippelsdorf49496032011-04-20 14:28:45 -0400515 return count;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700516}
517
Borislav Petkov39094442010-11-24 19:52:09 +0100518/*
519 * ->get_sdram_scrub_rate() return value semantics same as above.
520 */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300521static ssize_t mci_sdram_scrub_rate_show(struct device *dev,
522 struct device_attribute *mattr,
523 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700524{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300525 struct mem_ctl_info *mci = to_mci(dev);
Borislav Petkov39094442010-11-24 19:52:09 +0100526 int bandwidth = 0;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700527
Borislav Petkov39094442010-11-24 19:52:09 +0100528 if (!mci->get_sdram_scrub_rate)
Borislav Petkov5e8e19b2011-09-21 14:10:43 +0200529 return -ENODEV;
Borislav Petkov39094442010-11-24 19:52:09 +0100530
531 bandwidth = mci->get_sdram_scrub_rate(mci);
532 if (bandwidth < 0) {
533 edac_printk(KERN_DEBUG, EDAC_MC, "Error reading scrub rate\n");
534 return bandwidth;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700535 }
Borislav Petkoveba042a2010-05-25 18:21:07 +0200536
Borislav Petkov39094442010-11-24 19:52:09 +0100537 return sprintf(data, "%d\n", bandwidth);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700538}
539
540/* default attribute files for the MCI object */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300541static ssize_t mci_ue_count_show(struct device *dev,
542 struct device_attribute *mattr,
543 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700544{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300545 struct mem_ctl_info *mci = to_mci(dev);
546
Mauro Carvalho Chehab5926ff52012-02-09 11:05:20 -0300547 return sprintf(data, "%d\n", mci->ue_mc);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700548}
549
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300550static ssize_t mci_ce_count_show(struct device *dev,
551 struct device_attribute *mattr,
552 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700553{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300554 struct mem_ctl_info *mci = to_mci(dev);
555
Mauro Carvalho Chehab5926ff52012-02-09 11:05:20 -0300556 return sprintf(data, "%d\n", mci->ce_mc);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700557}
558
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300559static ssize_t mci_ce_noinfo_show(struct device *dev,
560 struct device_attribute *mattr,
561 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700562{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300563 struct mem_ctl_info *mci = to_mci(dev);
564
Douglas Thompson079708b2007-07-19 01:49:58 -0700565 return sprintf(data, "%d\n", mci->ce_noinfo_count);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700566}
567
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300568static ssize_t mci_ue_noinfo_show(struct device *dev,
569 struct device_attribute *mattr,
570 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700571{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300572 struct mem_ctl_info *mci = to_mci(dev);
573
Douglas Thompson079708b2007-07-19 01:49:58 -0700574 return sprintf(data, "%d\n", mci->ue_noinfo_count);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700575}
576
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300577static ssize_t mci_seconds_show(struct device *dev,
578 struct device_attribute *mattr,
579 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700580{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300581 struct mem_ctl_info *mci = to_mci(dev);
582
Douglas Thompson079708b2007-07-19 01:49:58 -0700583 return sprintf(data, "%ld\n", (jiffies - mci->start_time) / HZ);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700584}
585
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300586static ssize_t mci_ctl_name_show(struct device *dev,
587 struct device_attribute *mattr,
588 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700589{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300590 struct mem_ctl_info *mci = to_mci(dev);
591
Douglas Thompson079708b2007-07-19 01:49:58 -0700592 return sprintf(data, "%s\n", mci->ctl_name);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700593}
594
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300595static ssize_t mci_size_mb_show(struct device *dev,
596 struct device_attribute *mattr,
597 char *data)
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700598{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300599 struct mem_ctl_info *mci = to_mci(dev);
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300600 int total_pages = 0, csrow_idx, j;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700601
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300602 for (csrow_idx = 0; csrow_idx < mci->nr_csrows; csrow_idx++) {
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700603 struct csrow_info *csrow = &mci->csrows[csrow_idx];
604
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300605 for (j = 0; j < csrow->nr_channels; j++) {
606 struct dimm_info *dimm = csrow->channels[j].dimm;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700607
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300608 total_pages += dimm->nr_pages;
609 }
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700610 }
611
Douglas Thompson079708b2007-07-19 01:49:58 -0700612 return sprintf(data, "%u\n", PAGES_TO_MiB(total_pages));
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700613}
614
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700615/* default Control file */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300616DEVICE_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700617
618/* default Attribute files */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300619DEVICE_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL);
620DEVICE_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL);
621DEVICE_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL);
622DEVICE_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL);
623DEVICE_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL);
624DEVICE_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL);
625DEVICE_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700626
627/* memory scrubber attribute file */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300628DEVICE_ATTR(sdram_scrub_rate, S_IRUGO | S_IWUSR, mci_sdram_scrub_rate_show,
Douglas Thompson052dfb42007-07-19 01:50:13 -0700629 mci_sdram_scrub_rate_store);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700630
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300631static struct attribute *mci_attrs[] = {
632 &dev_attr_reset_counters.attr,
633 &dev_attr_mc_name.attr,
634 &dev_attr_size_mb.attr,
635 &dev_attr_seconds_since_reset.attr,
636 &dev_attr_ue_noinfo_count.attr,
637 &dev_attr_ce_noinfo_count.attr,
638 &dev_attr_ue_count.attr,
639 &dev_attr_ce_count.attr,
640 &dev_attr_sdram_scrub_rate.attr,
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700641 NULL
642};
643
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300644static struct attribute_group mci_attr_grp = {
645 .attrs = mci_attrs,
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700646};
647
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300648static const struct attribute_group *mci_attr_groups[] = {
649 &mci_attr_grp,
650 NULL
Mauro Carvalho Chehabcc301b32009-09-24 16:23:42 -0300651};
652
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300653static void mci_attr_release(struct device *device)
654{
655 debugf1("Releasing mci device %s\n", dev_name(device));
656}
657
658static struct device_type mci_attr_type = {
659 .groups = mci_attr_groups,
660 .release = mci_attr_release,
Mauro Carvalho Chehabcc301b32009-09-24 16:23:42 -0300661};
662
663
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700664/*
665 * Create a new Memory Controller kobject instance,
666 * mc<id> under the 'mc' directory
667 *
668 * Return:
669 * 0 Success
670 * !0 Failure
671 */
672int edac_create_sysfs_mci_device(struct mem_ctl_info *mci)
673{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300674 int i, err;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700675
676 debugf0("%s() idx=%d\n", __func__, mci->mc_idx);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700677
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300678 /* get the /sys/devices/system/edac subsys reference */
Mauro Carvalho Chehabb9687592009-09-25 13:42:25 -0300679
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300680 mci->dev.type = &mci_attr_type;
681 device_initialize(&mci->dev);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700682
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300683 mci->dev.parent = &mci_pdev;
684 mci->dev.bus = &mci->bus;
685 dev_set_name(&mci->dev, "mc%d", mci->mc_idx);
686 dev_set_drvdata(&mci->dev, mci);
687 pm_runtime_forbid(&mci->dev);
688
689 /*
690 * The memory controller needs its own bus, in order to avoid
691 * namespace conflicts at /sys/bus/edac.
Douglas Thompson42a8e392007-07-19 01:50:10 -0700692 */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300693 debugf0("creating bus %s\n",mci->bus.name);
694 mci->bus.name = kstrdup(dev_name(&mci->dev), GFP_KERNEL);
695 err = bus_register(&mci->bus);
696 if (err < 0)
697 return err;
698
699 debugf0("%s(): creating device %s\n", __func__,
700 dev_name(&mci->dev));
701 err = device_add(&mci->dev);
702 if (err < 0) {
703 bus_unregister(&mci->bus);
704 kfree(mci->bus.name);
705 return err;
Douglas Thompson42a8e392007-07-19 01:50:10 -0700706 }
707
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300708 /*
709 * Create the dimm/rank devices
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700710 */
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300711 for (i = 0; i < mci->tot_dimms; i++) {
712 struct dimm_info *dimm = &mci->dimms[i];
713 /* Only expose populated DIMMs */
714 if (dimm->nr_pages == 0)
715 continue;
716#ifdef CONFIG_EDAC_DEBUG
717 debugf1("%s creating dimm%d, located at ",
718 __func__, i);
719 if (edac_debug_level >= 1) {
720 int lay;
721 for (lay = 0; lay < mci->n_layers; lay++)
722 printk(KERN_CONT "%s %d ",
723 edac_layer_name[mci->layers[lay].type],
724 dimm->location[lay]);
725 printk(KERN_CONT "\n");
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700726 }
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300727#endif
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700728 }
729
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300730 err = edac_create_csrow_objects(mci);
731 if (err < 0)
732 goto fail;
733
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700734 return 0;
735
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300736fail:
Douglas Thompson079708b2007-07-19 01:49:58 -0700737 for (i--; i >= 0; i--) {
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300738 struct dimm_info *dimm = &mci->dimms[i];
739 if (dimm->nr_pages == 0)
740 continue;
741 put_device(&dimm->dev);
742 device_del(&dimm->dev);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700743 }
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300744 put_device(&mci->dev);
745 device_del(&mci->dev);
746 bus_unregister(&mci->bus);
747 kfree(mci->bus.name);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700748 return err;
749}
750
751/*
752 * remove a Memory Controller instance
753 */
754void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci)
755{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300756 int i;
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700757
758 debugf0("%s()\n", __func__);
759
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300760 edac_delete_csrow_objects(mci);
Mauro Carvalho Chehaba895bf82012-01-28 09:09:38 -0300761
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300762 for (i = 0; i < mci->tot_dimms; i++) {
763 struct dimm_info *dimm = &mci->dimms[i];
764 if (dimm->nr_pages == 0)
765 continue;
766 debugf0("%s(): removing device %s\n", __func__,
767 dev_name(&dimm->dev));
768 put_device(&dimm->dev);
769 device_del(&dimm->dev);
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700770 }
Douglas Thompson7c9281d2007-07-19 01:49:33 -0700771}
Doug Thompson8096cfa2007-07-19 01:50:27 -0700772
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300773void edac_unregister_sysfs(struct mem_ctl_info *mci)
Doug Thompson8096cfa2007-07-19 01:50:27 -0700774{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300775 debugf1("Unregistering device %s\n", dev_name(&mci->dev));
776 put_device(&mci->dev);
777 device_del(&mci->dev);
778 bus_unregister(&mci->bus);
779 kfree(mci->bus.name);
780}
Doug Thompson8096cfa2007-07-19 01:50:27 -0700781
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300782static void mc_attr_release(struct device *device)
783{
784 debugf1("Releasing device %s\n", dev_name(device));
785}
786
787static struct device_type mc_attr_type = {
788 .release = mc_attr_release,
789};
790/*
791 * Init/exit code for the module. Basically, creates/removes /sys/class/rc
792 */
793int __init edac_mc_sysfs_init(void)
794{
795 struct bus_type *edac_subsys;
796 int err;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700797
Kay Sieversfe5ff8b2011-12-14 15:21:07 -0800798 /* get the /sys/devices/system/edac subsys reference */
799 edac_subsys = edac_get_sysfs_subsys();
800 if (edac_subsys == NULL) {
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300801 debugf1("%s() no edac_subsys\n", __func__);
802 return -EINVAL;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700803 }
804
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300805 mci_pdev.bus = edac_subsys;
806 mci_pdev.type = &mc_attr_type;
807 device_initialize(&mci_pdev);
808 dev_set_name(&mci_pdev, "mc");
Doug Thompson8096cfa2007-07-19 01:50:27 -0700809
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300810 err = device_add(&mci_pdev);
811 if (err < 0)
812 return err;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700813
814 return 0;
Doug Thompson8096cfa2007-07-19 01:50:27 -0700815}
816
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300817void __exit edac_mc_sysfs_exit(void)
Doug Thompson8096cfa2007-07-19 01:50:27 -0700818{
Mauro Carvalho Chehab7a623c02012-04-16 16:41:11 -0300819 put_device(&mci_pdev);
820 device_del(&mci_pdev);
Kay Sieversfe5ff8b2011-12-14 15:21:07 -0800821 edac_put_sysfs_subsys();
Doug Thompson8096cfa2007-07-19 01:50:27 -0700822}