diff options
Diffstat (limited to 'drivers/edac/edac_mc_sysfs.c')
-rw-r--r-- | drivers/edac/edac_mc_sysfs.c | 1074 |
1 files changed, 407 insertions, 667 deletions
diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index 595371941ef9..7002c9cab999 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c | |||
@@ -7,17 +7,20 @@ | |||
7 | * | 7 | * |
8 | * Written Doug Thompson <norsk5@xmission.com> www.softwarebitmaker.com | 8 | * Written Doug Thompson <norsk5@xmission.com> www.softwarebitmaker.com |
9 | * | 9 | * |
10 | * (c) 2012 - Mauro Carvalho Chehab <mchehab@redhat.com> | ||
11 | * The entire API were re-written, and ported to use struct device | ||
12 | * | ||
10 | */ | 13 | */ |
11 | 14 | ||
12 | #include <linux/ctype.h> | 15 | #include <linux/ctype.h> |
13 | #include <linux/slab.h> | 16 | #include <linux/slab.h> |
14 | #include <linux/edac.h> | 17 | #include <linux/edac.h> |
15 | #include <linux/bug.h> | 18 | #include <linux/bug.h> |
19 | #include <linux/pm_runtime.h> | ||
16 | 20 | ||
17 | #include "edac_core.h" | 21 | #include "edac_core.h" |
18 | #include "edac_module.h" | 22 | #include "edac_module.h" |
19 | 23 | ||
20 | |||
21 | /* MC EDAC Controls, setable by module parameter, and sysfs */ | 24 | /* MC EDAC Controls, setable by module parameter, and sysfs */ |
22 | static int edac_mc_log_ue = 1; | 25 | static int edac_mc_log_ue = 1; |
23 | static int edac_mc_log_ce = 1; | 26 | static int edac_mc_log_ce = 1; |
@@ -78,6 +81,8 @@ module_param_call(edac_mc_poll_msec, edac_set_poll_msec, param_get_int, | |||
78 | &edac_mc_poll_msec, 0644); | 81 | &edac_mc_poll_msec, 0644); |
79 | MODULE_PARM_DESC(edac_mc_poll_msec, "Polling period in milliseconds"); | 82 | MODULE_PARM_DESC(edac_mc_poll_msec, "Polling period in milliseconds"); |
80 | 83 | ||
84 | static struct device mci_pdev; | ||
85 | |||
81 | /* | 86 | /* |
82 | * various constants for Memory Controllers | 87 | * various constants for Memory Controllers |
83 | */ | 88 | */ |
@@ -125,308 +130,336 @@ static const char *edac_caps[] = { | |||
125 | [EDAC_S16ECD16ED] = "S16ECD16ED" | 130 | [EDAC_S16ECD16ED] = "S16ECD16ED" |
126 | }; | 131 | }; |
127 | 132 | ||
128 | /* EDAC sysfs CSROW data structures and methods | 133 | /* |
134 | * EDAC sysfs CSROW data structures and methods | ||
135 | */ | ||
136 | |||
137 | #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 | ||
129 | */ | 142 | */ |
143 | #define DEVICE_ATTR_LEGACY(_name, _mode, _show, _store) \ | ||
144 | struct device_attribute dev_attr_legacy_##_name = __ATTR(_name, _mode, _show, _store) | ||
145 | |||
146 | struct 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) | ||
130 | 156 | ||
131 | /* Set of more default csrow<id> attribute show/store functions */ | 157 | /* Set of more default csrow<id> attribute show/store functions */ |
132 | static ssize_t csrow_ue_count_show(struct csrow_info *csrow, char *data, | 158 | static ssize_t csrow_ue_count_show(struct device *dev, |
133 | int private) | 159 | struct device_attribute *mattr, char *data) |
134 | { | 160 | { |
161 | struct csrow_info *csrow = to_csrow(dev); | ||
162 | |||
135 | return sprintf(data, "%u\n", csrow->ue_count); | 163 | return sprintf(data, "%u\n", csrow->ue_count); |
136 | } | 164 | } |
137 | 165 | ||
138 | static ssize_t csrow_ce_count_show(struct csrow_info *csrow, char *data, | 166 | static ssize_t csrow_ce_count_show(struct device *dev, |
139 | int private) | 167 | struct device_attribute *mattr, char *data) |
140 | { | 168 | { |
169 | struct csrow_info *csrow = to_csrow(dev); | ||
170 | |||
141 | return sprintf(data, "%u\n", csrow->ce_count); | 171 | return sprintf(data, "%u\n", csrow->ce_count); |
142 | } | 172 | } |
143 | 173 | ||
144 | static ssize_t csrow_size_show(struct csrow_info *csrow, char *data, | 174 | static ssize_t csrow_size_show(struct device *dev, |
145 | int private) | 175 | struct device_attribute *mattr, char *data) |
146 | { | 176 | { |
177 | struct csrow_info *csrow = to_csrow(dev); | ||
147 | int i; | 178 | int i; |
148 | u32 nr_pages = 0; | 179 | u32 nr_pages = 0; |
149 | 180 | ||
150 | for (i = 0; i < csrow->nr_channels; i++) | 181 | for (i = 0; i < csrow->nr_channels; i++) |
151 | nr_pages += csrow->channels[i].dimm->nr_pages; | 182 | nr_pages += csrow->channels[i].dimm->nr_pages; |
152 | |||
153 | return sprintf(data, "%u\n", PAGES_TO_MiB(nr_pages)); | 183 | return sprintf(data, "%u\n", PAGES_TO_MiB(nr_pages)); |
154 | } | 184 | } |
155 | 185 | ||
156 | static ssize_t csrow_mem_type_show(struct csrow_info *csrow, char *data, | 186 | static ssize_t csrow_mem_type_show(struct device *dev, |
157 | int private) | 187 | struct device_attribute *mattr, char *data) |
158 | { | 188 | { |
189 | struct csrow_info *csrow = to_csrow(dev); | ||
190 | |||
159 | return sprintf(data, "%s\n", mem_types[csrow->channels[0].dimm->mtype]); | 191 | return sprintf(data, "%s\n", mem_types[csrow->channels[0].dimm->mtype]); |
160 | } | 192 | } |
161 | 193 | ||
162 | static ssize_t csrow_dev_type_show(struct csrow_info *csrow, char *data, | 194 | static ssize_t csrow_dev_type_show(struct device *dev, |
163 | int private) | 195 | struct device_attribute *mattr, char *data) |
164 | { | 196 | { |
197 | struct csrow_info *csrow = to_csrow(dev); | ||
198 | |||
165 | return sprintf(data, "%s\n", dev_types[csrow->channels[0].dimm->dtype]); | 199 | return sprintf(data, "%s\n", dev_types[csrow->channels[0].dimm->dtype]); |
166 | } | 200 | } |
167 | 201 | ||
168 | static ssize_t csrow_edac_mode_show(struct csrow_info *csrow, char *data, | 202 | static ssize_t csrow_edac_mode_show(struct device *dev, |
169 | int private) | 203 | struct device_attribute *mattr, |
204 | char *data) | ||
170 | { | 205 | { |
206 | struct csrow_info *csrow = to_csrow(dev); | ||
207 | |||
171 | return sprintf(data, "%s\n", edac_caps[csrow->channels[0].dimm->edac_mode]); | 208 | return sprintf(data, "%s\n", edac_caps[csrow->channels[0].dimm->edac_mode]); |
172 | } | 209 | } |
173 | 210 | ||
174 | /* show/store functions for DIMM Label attributes */ | 211 | /* show/store functions for DIMM Label attributes */ |
175 | static ssize_t channel_dimm_label_show(struct csrow_info *csrow, | 212 | static ssize_t channel_dimm_label_show(struct device *dev, |
176 | char *data, int channel) | 213 | struct device_attribute *mattr, |
214 | char *data) | ||
177 | { | 215 | { |
216 | struct csrow_info *csrow = to_csrow(dev); | ||
217 | unsigned chan = to_channel(mattr); | ||
218 | struct rank_info *rank = &csrow->channels[chan]; | ||
219 | |||
178 | /* if field has not been initialized, there is nothing to send */ | 220 | /* if field has not been initialized, there is nothing to send */ |
179 | if (!csrow->channels[channel].dimm->label[0]) | 221 | if (!rank->dimm->label[0]) |
180 | return 0; | 222 | return 0; |
181 | 223 | ||
182 | return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n", | 224 | return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n", |
183 | csrow->channels[channel].dimm->label); | 225 | rank->dimm->label); |
184 | } | 226 | } |
185 | 227 | ||
186 | static ssize_t channel_dimm_label_store(struct csrow_info *csrow, | 228 | static ssize_t channel_dimm_label_store(struct device *dev, |
187 | const char *data, | 229 | struct device_attribute *mattr, |
188 | size_t count, int channel) | 230 | const char *data, size_t count) |
189 | { | 231 | { |
232 | struct csrow_info *csrow = to_csrow(dev); | ||
233 | unsigned chan = to_channel(mattr); | ||
234 | struct rank_info *rank = &csrow->channels[chan]; | ||
235 | |||
190 | ssize_t max_size = 0; | 236 | ssize_t max_size = 0; |
191 | 237 | ||
192 | max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1); | 238 | max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1); |
193 | strncpy(csrow->channels[channel].dimm->label, data, max_size); | 239 | strncpy(rank->dimm->label, data, max_size); |
194 | csrow->channels[channel].dimm->label[max_size] = '\0'; | 240 | rank->dimm->label[max_size] = '\0'; |
195 | 241 | ||
196 | return max_size; | 242 | return max_size; |
197 | } | 243 | } |
198 | 244 | ||
199 | /* show function for dynamic chX_ce_count attribute */ | 245 | /* show function for dynamic chX_ce_count attribute */ |
200 | static ssize_t channel_ce_count_show(struct csrow_info *csrow, | 246 | static ssize_t channel_ce_count_show(struct device *dev, |
201 | char *data, int channel) | 247 | struct device_attribute *mattr, char *data) |
202 | { | 248 | { |
203 | return sprintf(data, "%u\n", csrow->channels[channel].ce_count); | 249 | 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); | ||
204 | } | 254 | } |
205 | 255 | ||
206 | /* csrow specific attribute structure */ | 256 | /* cwrow<id>/attribute files */ |
207 | struct csrowdev_attribute { | 257 | DEVICE_ATTR_LEGACY(size_mb, S_IRUGO, csrow_size_show, NULL); |
208 | struct attribute attr; | 258 | DEVICE_ATTR_LEGACY(dev_type, S_IRUGO, csrow_dev_type_show, NULL); |
209 | ssize_t(*show) (struct csrow_info *, char *, int); | 259 | DEVICE_ATTR_LEGACY(mem_type, S_IRUGO, csrow_mem_type_show, NULL); |
210 | ssize_t(*store) (struct csrow_info *, const char *, size_t, int); | 260 | DEVICE_ATTR_LEGACY(edac_mode, S_IRUGO, csrow_edac_mode_show, NULL); |
211 | int private; | 261 | DEVICE_ATTR_LEGACY(ue_count, S_IRUGO, csrow_ue_count_show, NULL); |
212 | }; | 262 | DEVICE_ATTR_LEGACY(ce_count, S_IRUGO, csrow_ce_count_show, NULL); |
213 | 263 | ||
214 | #define to_csrow(k) container_of(k, struct csrow_info, kobj) | 264 | /* default attributes of the CSROW<id> object */ |
215 | #define to_csrowdev_attr(a) container_of(a, struct csrowdev_attribute, attr) | 265 | static 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, | ||
272 | NULL, | ||
273 | }; | ||
216 | 274 | ||
217 | /* Set of show/store higher level functions for default csrow attributes */ | 275 | static struct attribute_group csrow_attr_grp = { |
218 | static ssize_t csrowdev_show(struct kobject *kobj, | 276 | .attrs = csrow_attrs, |
219 | struct attribute *attr, char *buffer) | 277 | }; |
220 | { | ||
221 | struct csrow_info *csrow = to_csrow(kobj); | ||
222 | struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); | ||
223 | 278 | ||
224 | if (csrowdev_attr->show) | 279 | static const struct attribute_group *csrow_attr_groups[] = { |
225 | return csrowdev_attr->show(csrow, | 280 | &csrow_attr_grp, |
226 | buffer, csrowdev_attr->private); | 281 | NULL |
227 | return -EIO; | 282 | }; |
228 | } | ||
229 | 283 | ||
230 | static ssize_t csrowdev_store(struct kobject *kobj, struct attribute *attr, | 284 | static void csrow_attr_release(struct device *device) |
231 | const char *buffer, size_t count) | ||
232 | { | 285 | { |
233 | struct csrow_info *csrow = to_csrow(kobj); | 286 | debugf1("Releasing csrow device %s\n", dev_name(device)); |
234 | struct csrowdev_attribute *csrowdev_attr = to_csrowdev_attr(attr); | ||
235 | |||
236 | if (csrowdev_attr->store) | ||
237 | return csrowdev_attr->store(csrow, | ||
238 | buffer, | ||
239 | count, csrowdev_attr->private); | ||
240 | return -EIO; | ||
241 | } | 287 | } |
242 | 288 | ||
243 | static const struct sysfs_ops csrowfs_ops = { | 289 | static struct device_type csrow_attr_type = { |
244 | .show = csrowdev_show, | 290 | .groups = csrow_attr_groups, |
245 | .store = csrowdev_store | 291 | .release = csrow_attr_release, |
246 | }; | 292 | }; |
247 | 293 | ||
248 | #define CSROWDEV_ATTR(_name,_mode,_show,_store,_private) \ | 294 | /* |
249 | static struct csrowdev_attribute attr_##_name = { \ | 295 | * possible dynamic channel DIMM Label attribute files |
250 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | 296 | * |
251 | .show = _show, \ | 297 | */ |
252 | .store = _store, \ | ||
253 | .private = _private, \ | ||
254 | }; | ||
255 | |||
256 | /* default cwrow<id>/attribute files */ | ||
257 | CSROWDEV_ATTR(size_mb, S_IRUGO, csrow_size_show, NULL, 0); | ||
258 | CSROWDEV_ATTR(dev_type, S_IRUGO, csrow_dev_type_show, NULL, 0); | ||
259 | CSROWDEV_ATTR(mem_type, S_IRUGO, csrow_mem_type_show, NULL, 0); | ||
260 | CSROWDEV_ATTR(edac_mode, S_IRUGO, csrow_edac_mode_show, NULL, 0); | ||
261 | CSROWDEV_ATTR(ue_count, S_IRUGO, csrow_ue_count_show, NULL, 0); | ||
262 | CSROWDEV_ATTR(ce_count, S_IRUGO, csrow_ce_count_show, NULL, 0); | ||
263 | 298 | ||
264 | /* default attributes of the CSROW<id> object */ | 299 | #define EDAC_NR_CHANNELS 6 |
265 | static struct csrowdev_attribute *default_csrow_attr[] = { | ||
266 | &attr_dev_type, | ||
267 | &attr_mem_type, | ||
268 | &attr_edac_mode, | ||
269 | &attr_size_mb, | ||
270 | &attr_ue_count, | ||
271 | &attr_ce_count, | ||
272 | NULL, | ||
273 | }; | ||
274 | 300 | ||
275 | /* possible dynamic channel DIMM Label attribute files */ | 301 | DEVICE_CHANNEL(ch0_dimm_label, S_IRUGO | S_IWUSR, |
276 | CSROWDEV_ATTR(ch0_dimm_label, S_IRUGO | S_IWUSR, | ||
277 | channel_dimm_label_show, channel_dimm_label_store, 0); | 302 | channel_dimm_label_show, channel_dimm_label_store, 0); |
278 | CSROWDEV_ATTR(ch1_dimm_label, S_IRUGO | S_IWUSR, | 303 | DEVICE_CHANNEL(ch1_dimm_label, S_IRUGO | S_IWUSR, |
279 | channel_dimm_label_show, channel_dimm_label_store, 1); | 304 | channel_dimm_label_show, channel_dimm_label_store, 1); |
280 | CSROWDEV_ATTR(ch2_dimm_label, S_IRUGO | S_IWUSR, | 305 | DEVICE_CHANNEL(ch2_dimm_label, S_IRUGO | S_IWUSR, |
281 | channel_dimm_label_show, channel_dimm_label_store, 2); | 306 | channel_dimm_label_show, channel_dimm_label_store, 2); |
282 | CSROWDEV_ATTR(ch3_dimm_label, S_IRUGO | S_IWUSR, | 307 | DEVICE_CHANNEL(ch3_dimm_label, S_IRUGO | S_IWUSR, |
283 | channel_dimm_label_show, channel_dimm_label_store, 3); | 308 | channel_dimm_label_show, channel_dimm_label_store, 3); |
284 | CSROWDEV_ATTR(ch4_dimm_label, S_IRUGO | S_IWUSR, | 309 | DEVICE_CHANNEL(ch4_dimm_label, S_IRUGO | S_IWUSR, |
285 | channel_dimm_label_show, channel_dimm_label_store, 4); | 310 | channel_dimm_label_show, channel_dimm_label_store, 4); |
286 | CSROWDEV_ATTR(ch5_dimm_label, S_IRUGO | S_IWUSR, | 311 | DEVICE_CHANNEL(ch5_dimm_label, S_IRUGO | S_IWUSR, |
287 | channel_dimm_label_show, channel_dimm_label_store, 5); | 312 | channel_dimm_label_show, channel_dimm_label_store, 5); |
288 | 313 | ||
289 | /* Total possible dynamic DIMM Label attribute file table */ | 314 | /* Total possible dynamic DIMM Label attribute file table */ |
290 | static struct csrowdev_attribute *dynamic_csrow_dimm_attr[] = { | 315 | static struct device_attribute *dynamic_csrow_dimm_attr[] = { |
291 | &attr_ch0_dimm_label, | 316 | &dev_attr_legacy_ch0_dimm_label.attr, |
292 | &attr_ch1_dimm_label, | 317 | &dev_attr_legacy_ch1_dimm_label.attr, |
293 | &attr_ch2_dimm_label, | 318 | &dev_attr_legacy_ch2_dimm_label.attr, |
294 | &attr_ch3_dimm_label, | 319 | &dev_attr_legacy_ch3_dimm_label.attr, |
295 | &attr_ch4_dimm_label, | 320 | &dev_attr_legacy_ch4_dimm_label.attr, |
296 | &attr_ch5_dimm_label | 321 | &dev_attr_legacy_ch5_dimm_label.attr |
297 | }; | 322 | }; |
298 | 323 | ||
299 | /* possible dynamic channel ce_count attribute files */ | 324 | /* possible dynamic channel ce_count attribute files */ |
300 | CSROWDEV_ATTR(ch0_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 0); | 325 | DEVICE_CHANNEL(ch0_ce_count, S_IRUGO | S_IWUSR, |
301 | CSROWDEV_ATTR(ch1_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 1); | 326 | channel_ce_count_show, NULL, 0); |
302 | CSROWDEV_ATTR(ch2_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 2); | 327 | DEVICE_CHANNEL(ch1_ce_count, S_IRUGO | S_IWUSR, |
303 | CSROWDEV_ATTR(ch3_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 3); | 328 | channel_ce_count_show, NULL, 1); |
304 | CSROWDEV_ATTR(ch4_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 4); | 329 | DEVICE_CHANNEL(ch2_ce_count, S_IRUGO | S_IWUSR, |
305 | CSROWDEV_ATTR(ch5_ce_count, S_IRUGO | S_IWUSR, channel_ce_count_show, NULL, 5); | 330 | channel_ce_count_show, NULL, 2); |
331 | DEVICE_CHANNEL(ch3_ce_count, S_IRUGO | S_IWUSR, | ||
332 | channel_ce_count_show, NULL, 3); | ||
333 | DEVICE_CHANNEL(ch4_ce_count, S_IRUGO | S_IWUSR, | ||
334 | channel_ce_count_show, NULL, 4); | ||
335 | DEVICE_CHANNEL(ch5_ce_count, S_IRUGO | S_IWUSR, | ||
336 | channel_ce_count_show, NULL, 5); | ||
306 | 337 | ||
307 | /* Total possible dynamic ce_count attribute file table */ | 338 | /* Total possible dynamic ce_count attribute file table */ |
308 | static struct csrowdev_attribute *dynamic_csrow_ce_count_attr[] = { | 339 | static struct device_attribute *dynamic_csrow_ce_count_attr[] = { |
309 | &attr_ch0_ce_count, | 340 | &dev_attr_legacy_ch0_ce_count.attr, |
310 | &attr_ch1_ce_count, | 341 | &dev_attr_legacy_ch1_ce_count.attr, |
311 | &attr_ch2_ce_count, | 342 | &dev_attr_legacy_ch2_ce_count.attr, |
312 | &attr_ch3_ce_count, | 343 | &dev_attr_legacy_ch3_ce_count.attr, |
313 | &attr_ch4_ce_count, | 344 | &dev_attr_legacy_ch4_ce_count.attr, |
314 | &attr_ch5_ce_count | 345 | &dev_attr_legacy_ch5_ce_count.attr |
315 | }; | 346 | }; |
316 | 347 | ||
317 | #define EDAC_NR_CHANNELS 6 | 348 | /* Create a CSROW object under specifed edac_mc_device */ |
318 | 349 | static int edac_create_csrow_object(struct mem_ctl_info *mci, | |
319 | /* Create dynamic CHANNEL files, indexed by 'chan', under specifed CSROW */ | 350 | struct csrow_info *csrow, int index) |
320 | static int edac_create_channel_files(struct kobject *kobj, int chan) | ||
321 | { | 351 | { |
322 | int err = -ENODEV; | 352 | int err, chan; |
323 | 353 | ||
324 | if (chan >= EDAC_NR_CHANNELS) | 354 | if (csrow->nr_channels >= EDAC_NR_CHANNELS) |
325 | return err; | 355 | return -ENODEV; |
326 | 356 | ||
327 | /* create the DIMM label attribute file */ | 357 | csrow->dev.type = &csrow_attr_type; |
328 | err = sysfs_create_file(kobj, | 358 | csrow->dev.bus = &mci->bus; |
329 | (struct attribute *) | 359 | device_initialize(&csrow->dev); |
330 | dynamic_csrow_dimm_attr[chan]); | 360 | csrow->dev.parent = &mci->dev; |
331 | 361 | dev_set_name(&csrow->dev, "csrow%d", index); | |
332 | if (!err) { | 362 | dev_set_drvdata(&csrow->dev, csrow); |
333 | /* create the CE Count attribute file */ | ||
334 | err = sysfs_create_file(kobj, | ||
335 | (struct attribute *) | ||
336 | dynamic_csrow_ce_count_attr[chan]); | ||
337 | } else { | ||
338 | debugf1("%s() dimm labels and ce_count files created", | ||
339 | __func__); | ||
340 | } | ||
341 | 363 | ||
342 | return err; | 364 | debugf0("%s(): creating (virtual) csrow node %s\n", __func__, |
343 | } | 365 | dev_name(&csrow->dev)); |
344 | 366 | ||
345 | /* No memory to release for this kobj */ | 367 | err = device_add(&csrow->dev); |
346 | static void edac_csrow_instance_release(struct kobject *kobj) | 368 | if (err < 0) |
347 | { | 369 | return err; |
348 | struct mem_ctl_info *mci; | ||
349 | struct csrow_info *cs; | ||
350 | 370 | ||
351 | debugf1("%s()\n", __func__); | 371 | for (chan = 0; chan < csrow->nr_channels; chan++) { |
372 | 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; | ||
382 | } | ||
383 | } | ||
352 | 384 | ||
353 | cs = container_of(kobj, struct csrow_info, kobj); | 385 | return 0; |
354 | mci = cs->mci; | ||
355 | 386 | ||
356 | kobject_put(&mci->edac_mci_kobj); | 387 | error: |
357 | } | 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); | ||
358 | 395 | ||
359 | /* the kobj_type instance for a CSROW */ | 396 | return err; |
360 | static struct kobj_type ktype_csrow = { | 397 | } |
361 | .release = edac_csrow_instance_release, | ||
362 | .sysfs_ops = &csrowfs_ops, | ||
363 | .default_attrs = (struct attribute **)default_csrow_attr, | ||
364 | }; | ||
365 | 398 | ||
366 | /* Create a CSROW object under specifed edac_mc_device */ | 399 | /* Create a CSROW object under specifed edac_mc_device */ |
367 | static int edac_create_csrow_object(struct mem_ctl_info *mci, | 400 | static int edac_create_csrow_objects(struct mem_ctl_info *mci) |
368 | struct csrow_info *csrow, int index) | ||
369 | { | 401 | { |
370 | struct kobject *kobj_mci = &mci->edac_mci_kobj; | 402 | int err, i, chan; |
371 | struct kobject *kobj; | 403 | struct csrow_info *csrow; |
372 | int chan; | ||
373 | int err; | ||
374 | 404 | ||
375 | /* generate ..../edac/mc/mc<id>/csrow<index> */ | 405 | for (i = 0; i < mci->nr_csrows; i++) { |
376 | memset(&csrow->kobj, 0, sizeof(csrow->kobj)); | 406 | err = edac_create_csrow_object(mci, &mci->csrows[i], i); |
377 | csrow->mci = mci; /* include container up link */ | 407 | if (err < 0) |
408 | goto error; | ||
409 | } | ||
410 | return 0; | ||
378 | 411 | ||
379 | /* bump the mci instance's kobject's ref count */ | 412 | error: |
380 | kobj = kobject_get(&mci->edac_mci_kobj); | 413 | for (--i; i >= 0; i--) { |
381 | if (!kobj) { | 414 | csrow = &mci->csrows[i]; |
382 | err = -ENODEV; | 415 | for (chan = csrow->nr_channels - 1; chan >= 0; chan--) { |
383 | goto err_out; | 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); | ||
384 | } | 422 | } |
385 | 423 | ||
386 | /* Instanstiate the csrow object */ | 424 | return err; |
387 | err = kobject_init_and_add(&csrow->kobj, &ktype_csrow, kobj_mci, | 425 | } |
388 | "csrow%d", index); | ||
389 | if (err) | ||
390 | goto err_release_top_kobj; | ||
391 | 426 | ||
392 | /* At this point, to release a csrow kobj, one must | 427 | static void edac_delete_csrow_objects(struct mem_ctl_info *mci) |
393 | * call the kobject_put and allow that tear down | 428 | { |
394 | * to work the releasing | 429 | int i, chan; |
395 | */ | 430 | struct csrow_info *csrow; |
396 | 431 | ||
397 | /* Create the dyanmic attribute files on this csrow, | 432 | for (i = mci->nr_csrows - 1; i >= 0; i--) { |
398 | * namely, the DIMM labels and the channel ce_count | 433 | csrow = &mci->csrows[i]; |
399 | */ | 434 | for (chan = csrow->nr_channels - 1; chan >= 0; chan--) { |
400 | for (chan = 0; chan < csrow->nr_channels; chan++) { | 435 | debugf1("Removing csrow %d channel %d sysfs nodes\n", |
401 | err = edac_create_channel_files(&csrow->kobj, chan); | 436 | i, chan); |
402 | if (err) { | 437 | device_remove_file(&csrow->dev, |
403 | /* special case the unregister here */ | 438 | dynamic_csrow_dimm_attr[chan]); |
404 | kobject_put(&csrow->kobj); | 439 | device_remove_file(&csrow->dev, |
405 | goto err_out; | 440 | dynamic_csrow_ce_count_attr[chan]); |
406 | } | 441 | } |
442 | put_device(&mci->csrows[i].dev); | ||
443 | device_del(&mci->csrows[i].dev); | ||
407 | } | 444 | } |
408 | kobject_uevent(&csrow->kobj, KOBJ_ADD); | ||
409 | return 0; | ||
410 | |||
411 | /* error unwind stack */ | ||
412 | err_release_top_kobj: | ||
413 | kobject_put(&mci->edac_mci_kobj); | ||
414 | |||
415 | err_out: | ||
416 | return err; | ||
417 | } | 445 | } |
418 | 446 | ||
419 | /* default sysfs methods and data structures for the main MCI kobject */ | 447 | /* |
448 | * Memory controller device | ||
449 | */ | ||
450 | |||
451 | #define to_mci(k) container_of(k, struct mem_ctl_info, dev) | ||
420 | 452 | ||
421 | static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, | 453 | static ssize_t mci_reset_counters_store(struct device *dev, |
454 | struct device_attribute *mattr, | ||
422 | const char *data, size_t count) | 455 | const char *data, size_t count) |
423 | { | 456 | { |
424 | int row, chan; | 457 | struct mem_ctl_info *mci = to_mci(dev); |
425 | 458 | int cnt, row, chan, i; | |
426 | mci->ue_noinfo_count = 0; | ||
427 | mci->ce_noinfo_count = 0; | ||
428 | mci->ue_mc = 0; | 459 | mci->ue_mc = 0; |
429 | mci->ce_mc = 0; | 460 | mci->ce_mc = 0; |
461 | mci->ue_noinfo_count = 0; | ||
462 | mci->ce_noinfo_count = 0; | ||
430 | 463 | ||
431 | for (row = 0; row < mci->nr_csrows; row++) { | 464 | for (row = 0; row < mci->nr_csrows; row++) { |
432 | struct csrow_info *ri = &mci->csrows[row]; | 465 | struct csrow_info *ri = &mci->csrows[row]; |
@@ -438,6 +471,13 @@ static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, | |||
438 | ri->channels[chan].ce_count = 0; | 471 | ri->channels[chan].ce_count = 0; |
439 | } | 472 | } |
440 | 473 | ||
474 | 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 | |||
441 | mci->start_time = jiffies; | 481 | mci->start_time = jiffies; |
442 | return count; | 482 | return count; |
443 | } | 483 | } |
@@ -451,9 +491,11 @@ static ssize_t mci_reset_counters_store(struct mem_ctl_info *mci, | |||
451 | * Negative value still means that an error has occurred while setting | 491 | * Negative value still means that an error has occurred while setting |
452 | * the scrub rate. | 492 | * the scrub rate. |
453 | */ | 493 | */ |
454 | static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, | 494 | static ssize_t mci_sdram_scrub_rate_store(struct device *dev, |
495 | struct device_attribute *mattr, | ||
455 | const char *data, size_t count) | 496 | const char *data, size_t count) |
456 | { | 497 | { |
498 | struct mem_ctl_info *mci = to_mci(dev); | ||
457 | unsigned long bandwidth = 0; | 499 | unsigned long bandwidth = 0; |
458 | int new_bw = 0; | 500 | int new_bw = 0; |
459 | 501 | ||
@@ -476,8 +518,11 @@ static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, | |||
476 | /* | 518 | /* |
477 | * ->get_sdram_scrub_rate() return value semantics same as above. | 519 | * ->get_sdram_scrub_rate() return value semantics same as above. |
478 | */ | 520 | */ |
479 | static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) | 521 | static ssize_t mci_sdram_scrub_rate_show(struct device *dev, |
522 | struct device_attribute *mattr, | ||
523 | char *data) | ||
480 | { | 524 | { |
525 | struct mem_ctl_info *mci = to_mci(dev); | ||
481 | int bandwidth = 0; | 526 | int bandwidth = 0; |
482 | 527 | ||
483 | if (!mci->get_sdram_scrub_rate) | 528 | if (!mci->get_sdram_scrub_rate) |
@@ -493,38 +538,65 @@ static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) | |||
493 | } | 538 | } |
494 | 539 | ||
495 | /* default attribute files for the MCI object */ | 540 | /* default attribute files for the MCI object */ |
496 | static ssize_t mci_ue_count_show(struct mem_ctl_info *mci, char *data) | 541 | static ssize_t mci_ue_count_show(struct device *dev, |
542 | struct device_attribute *mattr, | ||
543 | char *data) | ||
497 | { | 544 | { |
545 | struct mem_ctl_info *mci = to_mci(dev); | ||
546 | |||
498 | return sprintf(data, "%d\n", mci->ue_mc); | 547 | return sprintf(data, "%d\n", mci->ue_mc); |
499 | } | 548 | } |
500 | 549 | ||
501 | static ssize_t mci_ce_count_show(struct mem_ctl_info *mci, char *data) | 550 | static ssize_t mci_ce_count_show(struct device *dev, |
551 | struct device_attribute *mattr, | ||
552 | char *data) | ||
502 | { | 553 | { |
554 | struct mem_ctl_info *mci = to_mci(dev); | ||
555 | |||
503 | return sprintf(data, "%d\n", mci->ce_mc); | 556 | return sprintf(data, "%d\n", mci->ce_mc); |
504 | } | 557 | } |
505 | 558 | ||
506 | static ssize_t mci_ce_noinfo_show(struct mem_ctl_info *mci, char *data) | 559 | static ssize_t mci_ce_noinfo_show(struct device *dev, |
560 | struct device_attribute *mattr, | ||
561 | char *data) | ||
507 | { | 562 | { |
563 | struct mem_ctl_info *mci = to_mci(dev); | ||
564 | |||
508 | return sprintf(data, "%d\n", mci->ce_noinfo_count); | 565 | return sprintf(data, "%d\n", mci->ce_noinfo_count); |
509 | } | 566 | } |
510 | 567 | ||
511 | static ssize_t mci_ue_noinfo_show(struct mem_ctl_info *mci, char *data) | 568 | static ssize_t mci_ue_noinfo_show(struct device *dev, |
569 | struct device_attribute *mattr, | ||
570 | char *data) | ||
512 | { | 571 | { |
572 | struct mem_ctl_info *mci = to_mci(dev); | ||
573 | |||
513 | return sprintf(data, "%d\n", mci->ue_noinfo_count); | 574 | return sprintf(data, "%d\n", mci->ue_noinfo_count); |
514 | } | 575 | } |
515 | 576 | ||
516 | static ssize_t mci_seconds_show(struct mem_ctl_info *mci, char *data) | 577 | static ssize_t mci_seconds_show(struct device *dev, |
578 | struct device_attribute *mattr, | ||
579 | char *data) | ||
517 | { | 580 | { |
581 | struct mem_ctl_info *mci = to_mci(dev); | ||
582 | |||
518 | return sprintf(data, "%ld\n", (jiffies - mci->start_time) / HZ); | 583 | return sprintf(data, "%ld\n", (jiffies - mci->start_time) / HZ); |
519 | } | 584 | } |
520 | 585 | ||
521 | static ssize_t mci_ctl_name_show(struct mem_ctl_info *mci, char *data) | 586 | static ssize_t mci_ctl_name_show(struct device *dev, |
587 | struct device_attribute *mattr, | ||
588 | char *data) | ||
522 | { | 589 | { |
590 | struct mem_ctl_info *mci = to_mci(dev); | ||
591 | |||
523 | return sprintf(data, "%s\n", mci->ctl_name); | 592 | return sprintf(data, "%s\n", mci->ctl_name); |
524 | } | 593 | } |
525 | 594 | ||
526 | static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) | 595 | static ssize_t mci_size_mb_show(struct device *dev, |
596 | struct device_attribute *mattr, | ||
597 | char *data) | ||
527 | { | 598 | { |
599 | struct mem_ctl_info *mci = to_mci(dev); | ||
528 | int total_pages = 0, csrow_idx, j; | 600 | int total_pages = 0, csrow_idx, j; |
529 | 601 | ||
530 | for (csrow_idx = 0; csrow_idx < mci->nr_csrows; csrow_idx++) { | 602 | for (csrow_idx = 0; csrow_idx < mci->nr_csrows; csrow_idx++) { |
@@ -540,360 +612,53 @@ static ssize_t mci_size_mb_show(struct mem_ctl_info *mci, char *data) | |||
540 | return sprintf(data, "%u\n", PAGES_TO_MiB(total_pages)); | 612 | return sprintf(data, "%u\n", PAGES_TO_MiB(total_pages)); |
541 | } | 613 | } |
542 | 614 | ||
543 | #define to_mci(k) container_of(k, struct mem_ctl_info, edac_mci_kobj) | ||
544 | #define to_mcidev_attr(a) container_of(a,struct mcidev_sysfs_attribute,attr) | ||
545 | |||
546 | /* MCI show/store functions for top most object */ | ||
547 | static ssize_t mcidev_show(struct kobject *kobj, struct attribute *attr, | ||
548 | char *buffer) | ||
549 | { | ||
550 | struct mem_ctl_info *mem_ctl_info = to_mci(kobj); | ||
551 | struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); | ||
552 | |||
553 | debugf1("%s() mem_ctl_info %p\n", __func__, mem_ctl_info); | ||
554 | |||
555 | if (mcidev_attr->show) | ||
556 | return mcidev_attr->show(mem_ctl_info, buffer); | ||
557 | |||
558 | return -EIO; | ||
559 | } | ||
560 | |||
561 | static ssize_t mcidev_store(struct kobject *kobj, struct attribute *attr, | ||
562 | const char *buffer, size_t count) | ||
563 | { | ||
564 | struct mem_ctl_info *mem_ctl_info = to_mci(kobj); | ||
565 | struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); | ||
566 | |||
567 | debugf1("%s() mem_ctl_info %p\n", __func__, mem_ctl_info); | ||
568 | |||
569 | if (mcidev_attr->store) | ||
570 | return mcidev_attr->store(mem_ctl_info, buffer, count); | ||
571 | |||
572 | return -EIO; | ||
573 | } | ||
574 | |||
575 | /* Intermediate show/store table */ | ||
576 | static const struct sysfs_ops mci_ops = { | ||
577 | .show = mcidev_show, | ||
578 | .store = mcidev_store | ||
579 | }; | ||
580 | |||
581 | #define MCIDEV_ATTR(_name,_mode,_show,_store) \ | ||
582 | static struct mcidev_sysfs_attribute mci_attr_##_name = { \ | ||
583 | .attr = {.name = __stringify(_name), .mode = _mode }, \ | ||
584 | .show = _show, \ | ||
585 | .store = _store, \ | ||
586 | }; | ||
587 | |||
588 | /* default Control file */ | 615 | /* default Control file */ |
589 | MCIDEV_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store); | 616 | DEVICE_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store); |
590 | 617 | ||
591 | /* default Attribute files */ | 618 | /* default Attribute files */ |
592 | MCIDEV_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL); | 619 | DEVICE_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL); |
593 | MCIDEV_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL); | 620 | DEVICE_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL); |
594 | MCIDEV_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL); | 621 | DEVICE_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL); |
595 | MCIDEV_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL); | 622 | DEVICE_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL); |
596 | MCIDEV_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL); | 623 | DEVICE_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL); |
597 | MCIDEV_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL); | 624 | DEVICE_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL); |
598 | MCIDEV_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL); | 625 | DEVICE_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL); |
599 | 626 | ||
600 | /* memory scrubber attribute file */ | 627 | /* memory scrubber attribute file */ |
601 | MCIDEV_ATTR(sdram_scrub_rate, S_IRUGO | S_IWUSR, mci_sdram_scrub_rate_show, | 628 | DEVICE_ATTR(sdram_scrub_rate, S_IRUGO | S_IWUSR, mci_sdram_scrub_rate_show, |
602 | mci_sdram_scrub_rate_store); | 629 | mci_sdram_scrub_rate_store); |
603 | 630 | ||
604 | static struct mcidev_sysfs_attribute *mci_attr[] = { | 631 | static struct attribute *mci_attrs[] = { |
605 | &mci_attr_reset_counters, | 632 | &dev_attr_reset_counters.attr, |
606 | &mci_attr_mc_name, | 633 | &dev_attr_mc_name.attr, |
607 | &mci_attr_size_mb, | 634 | &dev_attr_size_mb.attr, |
608 | &mci_attr_seconds_since_reset, | 635 | &dev_attr_seconds_since_reset.attr, |
609 | &mci_attr_ue_noinfo_count, | 636 | &dev_attr_ue_noinfo_count.attr, |
610 | &mci_attr_ce_noinfo_count, | 637 | &dev_attr_ce_noinfo_count.attr, |
611 | &mci_attr_ue_count, | 638 | &dev_attr_ue_count.attr, |
612 | &mci_attr_ce_count, | 639 | &dev_attr_ce_count.attr, |
613 | &mci_attr_sdram_scrub_rate, | 640 | &dev_attr_sdram_scrub_rate.attr, |
614 | NULL | 641 | NULL |
615 | }; | 642 | }; |
616 | 643 | ||
617 | 644 | static struct attribute_group mci_attr_grp = { | |
618 | /* | 645 | .attrs = mci_attrs, |
619 | * Release of a MC controlling instance | ||
620 | * | ||
621 | * each MC control instance has the following resources upon entry: | ||
622 | * a) a ref count on the top memctl kobj | ||
623 | * b) a ref count on this module | ||
624 | * | ||
625 | * this function must decrement those ref counts and then | ||
626 | * issue a free on the instance's memory | ||
627 | */ | ||
628 | static void edac_mci_control_release(struct kobject *kobj) | ||
629 | { | ||
630 | struct mem_ctl_info *mci; | ||
631 | |||
632 | mci = to_mci(kobj); | ||
633 | |||
634 | debugf0("%s() mci instance idx=%d releasing\n", __func__, mci->mc_idx); | ||
635 | |||
636 | /* decrement the module ref count */ | ||
637 | module_put(mci->owner); | ||
638 | } | ||
639 | |||
640 | static struct kobj_type ktype_mci = { | ||
641 | .release = edac_mci_control_release, | ||
642 | .sysfs_ops = &mci_ops, | ||
643 | .default_attrs = (struct attribute **)mci_attr, | ||
644 | }; | ||
645 | |||
646 | /* EDAC memory controller sysfs kset: | ||
647 | * /sys/devices/system/edac/mc | ||
648 | */ | ||
649 | static struct kset *mc_kset; | ||
650 | |||
651 | /* | ||
652 | * edac_mc_register_sysfs_main_kobj | ||
653 | * | ||
654 | * setups and registers the main kobject for each mci | ||
655 | */ | ||
656 | int edac_mc_register_sysfs_main_kobj(struct mem_ctl_info *mci) | ||
657 | { | ||
658 | struct kobject *kobj_mci; | ||
659 | int err; | ||
660 | |||
661 | debugf1("%s()\n", __func__); | ||
662 | |||
663 | kobj_mci = &mci->edac_mci_kobj; | ||
664 | |||
665 | /* Init the mci's kobject */ | ||
666 | memset(kobj_mci, 0, sizeof(*kobj_mci)); | ||
667 | |||
668 | /* Record which module 'owns' this control structure | ||
669 | * and bump the ref count of the module | ||
670 | */ | ||
671 | mci->owner = THIS_MODULE; | ||
672 | |||
673 | /* bump ref count on this module */ | ||
674 | if (!try_module_get(mci->owner)) { | ||
675 | err = -ENODEV; | ||
676 | goto fail_out; | ||
677 | } | ||
678 | |||
679 | /* this instance become part of the mc_kset */ | ||
680 | kobj_mci->kset = mc_kset; | ||
681 | |||
682 | /* register the mc<id> kobject to the mc_kset */ | ||
683 | err = kobject_init_and_add(kobj_mci, &ktype_mci, NULL, | ||
684 | "mc%d", mci->mc_idx); | ||
685 | if (err) { | ||
686 | debugf1("%s()Failed to register '.../edac/mc%d'\n", | ||
687 | __func__, mci->mc_idx); | ||
688 | goto kobj_reg_fail; | ||
689 | } | ||
690 | kobject_uevent(kobj_mci, KOBJ_ADD); | ||
691 | |||
692 | /* At this point, to 'free' the control struct, | ||
693 | * edac_mc_unregister_sysfs_main_kobj() must be used | ||
694 | */ | ||
695 | |||
696 | debugf1("%s() Registered '.../edac/mc%d' kobject\n", | ||
697 | __func__, mci->mc_idx); | ||
698 | |||
699 | return 0; | ||
700 | |||
701 | /* Error exit stack */ | ||
702 | |||
703 | kobj_reg_fail: | ||
704 | module_put(mci->owner); | ||
705 | |||
706 | fail_out: | ||
707 | return err; | ||
708 | } | ||
709 | |||
710 | /* | ||
711 | * edac_mc_register_sysfs_main_kobj | ||
712 | * | ||
713 | * tears down and the main mci kobject from the mc_kset | ||
714 | */ | ||
715 | void edac_mc_unregister_sysfs_main_kobj(struct mem_ctl_info *mci) | ||
716 | { | ||
717 | debugf1("%s()\n", __func__); | ||
718 | |||
719 | /* delete the kobj from the mc_kset */ | ||
720 | kobject_put(&mci->edac_mci_kobj); | ||
721 | } | ||
722 | |||
723 | #define EDAC_DEVICE_SYMLINK "device" | ||
724 | |||
725 | #define grp_to_mci(k) (container_of(k, struct mcidev_sysfs_group_kobj, kobj)->mci) | ||
726 | |||
727 | /* MCI show/store functions for top most object */ | ||
728 | static ssize_t inst_grp_show(struct kobject *kobj, struct attribute *attr, | ||
729 | char *buffer) | ||
730 | { | ||
731 | struct mem_ctl_info *mem_ctl_info = grp_to_mci(kobj); | ||
732 | struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); | ||
733 | |||
734 | debugf1("%s() mem_ctl_info %p\n", __func__, mem_ctl_info); | ||
735 | |||
736 | if (mcidev_attr->show) | ||
737 | return mcidev_attr->show(mem_ctl_info, buffer); | ||
738 | |||
739 | return -EIO; | ||
740 | } | ||
741 | |||
742 | static ssize_t inst_grp_store(struct kobject *kobj, struct attribute *attr, | ||
743 | const char *buffer, size_t count) | ||
744 | { | ||
745 | struct mem_ctl_info *mem_ctl_info = grp_to_mci(kobj); | ||
746 | struct mcidev_sysfs_attribute *mcidev_attr = to_mcidev_attr(attr); | ||
747 | |||
748 | debugf1("%s() mem_ctl_info %p\n", __func__, mem_ctl_info); | ||
749 | |||
750 | if (mcidev_attr->store) | ||
751 | return mcidev_attr->store(mem_ctl_info, buffer, count); | ||
752 | |||
753 | return -EIO; | ||
754 | } | ||
755 | |||
756 | /* No memory to release for this kobj */ | ||
757 | static void edac_inst_grp_release(struct kobject *kobj) | ||
758 | { | ||
759 | struct mcidev_sysfs_group_kobj *grp; | ||
760 | struct mem_ctl_info *mci; | ||
761 | |||
762 | debugf1("%s()\n", __func__); | ||
763 | |||
764 | grp = container_of(kobj, struct mcidev_sysfs_group_kobj, kobj); | ||
765 | mci = grp->mci; | ||
766 | } | ||
767 | |||
768 | /* Intermediate show/store table */ | ||
769 | static struct sysfs_ops inst_grp_ops = { | ||
770 | .show = inst_grp_show, | ||
771 | .store = inst_grp_store | ||
772 | }; | 646 | }; |
773 | 647 | ||
774 | /* the kobj_type instance for a instance group */ | 648 | static const struct attribute_group *mci_attr_groups[] = { |
775 | static struct kobj_type ktype_inst_grp = { | 649 | &mci_attr_grp, |
776 | .release = edac_inst_grp_release, | 650 | NULL |
777 | .sysfs_ops = &inst_grp_ops, | ||
778 | }; | 651 | }; |
779 | 652 | ||
780 | 653 | static void mci_attr_release(struct device *device) | |
781 | /* | ||
782 | * edac_create_mci_instance_attributes | ||
783 | * create MC driver specific attributes bellow an specified kobj | ||
784 | * This routine calls itself recursively, in order to create an entire | ||
785 | * object tree. | ||
786 | */ | ||
787 | static int edac_create_mci_instance_attributes(struct mem_ctl_info *mci, | ||
788 | const struct mcidev_sysfs_attribute *sysfs_attrib, | ||
789 | struct kobject *kobj) | ||
790 | { | 654 | { |
791 | int err; | 655 | debugf1("Releasing mci device %s\n", dev_name(device)); |
792 | |||
793 | debugf4("%s()\n", __func__); | ||
794 | |||
795 | while (sysfs_attrib) { | ||
796 | debugf4("%s() sysfs_attrib = %p\n",__func__, sysfs_attrib); | ||
797 | if (sysfs_attrib->grp) { | ||
798 | struct mcidev_sysfs_group_kobj *grp_kobj; | ||
799 | |||
800 | grp_kobj = kzalloc(sizeof(*grp_kobj), GFP_KERNEL); | ||
801 | if (!grp_kobj) | ||
802 | return -ENOMEM; | ||
803 | |||
804 | grp_kobj->grp = sysfs_attrib->grp; | ||
805 | grp_kobj->mci = mci; | ||
806 | list_add_tail(&grp_kobj->list, &mci->grp_kobj_list); | ||
807 | |||
808 | debugf0("%s() grp %s, mci %p\n", __func__, | ||
809 | sysfs_attrib->grp->name, mci); | ||
810 | |||
811 | err = kobject_init_and_add(&grp_kobj->kobj, | ||
812 | &ktype_inst_grp, | ||
813 | &mci->edac_mci_kobj, | ||
814 | sysfs_attrib->grp->name); | ||
815 | if (err < 0) { | ||
816 | printk(KERN_ERR "kobject_init_and_add failed: %d\n", err); | ||
817 | return err; | ||
818 | } | ||
819 | err = edac_create_mci_instance_attributes(mci, | ||
820 | grp_kobj->grp->mcidev_attr, | ||
821 | &grp_kobj->kobj); | ||
822 | |||
823 | if (err < 0) | ||
824 | return err; | ||
825 | } else if (sysfs_attrib->attr.name) { | ||
826 | debugf4("%s() file %s\n", __func__, | ||
827 | sysfs_attrib->attr.name); | ||
828 | |||
829 | err = sysfs_create_file(kobj, &sysfs_attrib->attr); | ||
830 | if (err < 0) { | ||
831 | printk(KERN_ERR "sysfs_create_file failed: %d\n", err); | ||
832 | return err; | ||
833 | } | ||
834 | } else | ||
835 | break; | ||
836 | |||
837 | sysfs_attrib++; | ||
838 | } | ||
839 | |||
840 | return 0; | ||
841 | } | 656 | } |
842 | 657 | ||
843 | /* | 658 | static struct device_type mci_attr_type = { |
844 | * edac_remove_mci_instance_attributes | 659 | .groups = mci_attr_groups, |
845 | * remove MC driver specific attributes at the topmost level | 660 | .release = mci_attr_release, |
846 | * directory of this mci instance. | 661 | }; |
847 | */ | ||
848 | static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, | ||
849 | const struct mcidev_sysfs_attribute *sysfs_attrib, | ||
850 | struct kobject *kobj, int count) | ||
851 | { | ||
852 | struct mcidev_sysfs_group_kobj *grp_kobj, *tmp; | ||
853 | |||
854 | debugf1("%s()\n", __func__); | ||
855 | |||
856 | /* | ||
857 | * loop if there are attributes and until we hit a NULL entry | ||
858 | * Remove first all the attributes | ||
859 | */ | ||
860 | while (sysfs_attrib) { | ||
861 | debugf4("%s() sysfs_attrib = %p\n",__func__, sysfs_attrib); | ||
862 | if (sysfs_attrib->grp) { | ||
863 | debugf4("%s() seeking for group %s\n", | ||
864 | __func__, sysfs_attrib->grp->name); | ||
865 | list_for_each_entry(grp_kobj, | ||
866 | &mci->grp_kobj_list, list) { | ||
867 | debugf4("%s() grp_kobj->grp = %p\n",__func__, grp_kobj->grp); | ||
868 | if (grp_kobj->grp == sysfs_attrib->grp) { | ||
869 | edac_remove_mci_instance_attributes(mci, | ||
870 | grp_kobj->grp->mcidev_attr, | ||
871 | &grp_kobj->kobj, count + 1); | ||
872 | debugf4("%s() group %s\n", __func__, | ||
873 | sysfs_attrib->grp->name); | ||
874 | kobject_put(&grp_kobj->kobj); | ||
875 | } | ||
876 | } | ||
877 | debugf4("%s() end of seeking for group %s\n", | ||
878 | __func__, sysfs_attrib->grp->name); | ||
879 | } else if (sysfs_attrib->attr.name) { | ||
880 | debugf4("%s() file %s\n", __func__, | ||
881 | sysfs_attrib->attr.name); | ||
882 | sysfs_remove_file(kobj, &sysfs_attrib->attr); | ||
883 | } else | ||
884 | break; | ||
885 | sysfs_attrib++; | ||
886 | } | ||
887 | |||
888 | /* Remove the group objects */ | ||
889 | if (count) | ||
890 | return; | ||
891 | list_for_each_entry_safe(grp_kobj, tmp, | ||
892 | &mci->grp_kobj_list, list) { | ||
893 | list_del(&grp_kobj->list); | ||
894 | kfree(grp_kobj); | ||
895 | } | ||
896 | } | ||
897 | 662 | ||
898 | 663 | ||
899 | /* | 664 | /* |
@@ -906,77 +671,80 @@ static void edac_remove_mci_instance_attributes(struct mem_ctl_info *mci, | |||
906 | */ | 671 | */ |
907 | int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) | 672 | int edac_create_sysfs_mci_device(struct mem_ctl_info *mci) |
908 | { | 673 | { |
909 | int i, j; | 674 | int i, err; |
910 | int err; | ||
911 | struct csrow_info *csrow; | ||
912 | struct kobject *kobj_mci = &mci->edac_mci_kobj; | ||
913 | 675 | ||
914 | debugf0("%s() idx=%d\n", __func__, mci->mc_idx); | 676 | debugf0("%s() idx=%d\n", __func__, mci->mc_idx); |
915 | 677 | ||
916 | INIT_LIST_HEAD(&mci->grp_kobj_list); | 678 | /* get the /sys/devices/system/edac subsys reference */ |
917 | 679 | ||
918 | /* create a symlink for the device */ | 680 | mci->dev.type = &mci_attr_type; |
919 | err = sysfs_create_link(kobj_mci, &mci->pdev->kobj, | 681 | device_initialize(&mci->dev); |
920 | EDAC_DEVICE_SYMLINK); | ||
921 | if (err) { | ||
922 | debugf1("%s() failure to create symlink\n", __func__); | ||
923 | goto fail0; | ||
924 | } | ||
925 | 682 | ||
926 | /* If the low level driver desires some attributes, | 683 | mci->dev.parent = &mci_pdev; |
927 | * then create them now for the driver. | 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. | ||
928 | */ | 692 | */ |
929 | if (mci->mc_driver_sysfs_attributes) { | 693 | debugf0("creating bus %s\n",mci->bus.name); |
930 | err = edac_create_mci_instance_attributes(mci, | 694 | mci->bus.name = kstrdup(dev_name(&mci->dev), GFP_KERNEL); |
931 | mci->mc_driver_sysfs_attributes, | 695 | err = bus_register(&mci->bus); |
932 | &mci->edac_mci_kobj); | 696 | if (err < 0) |
933 | if (err) { | 697 | return err; |
934 | debugf1("%s() failure to create mci attributes\n", | 698 | |
935 | __func__); | 699 | debugf0("%s(): creating device %s\n", __func__, |
936 | goto fail0; | 700 | dev_name(&mci->dev)); |
937 | } | 701 | err = device_add(&mci->dev); |
702 | if (err < 0) { | ||
703 | bus_unregister(&mci->bus); | ||
704 | kfree(mci->bus.name); | ||
705 | return err; | ||
938 | } | 706 | } |
939 | 707 | ||
940 | /* Make directories for each CSROW object under the mc<id> kobject | 708 | /* |
709 | * Create the dimm/rank devices | ||
941 | */ | 710 | */ |
942 | for (i = 0; i < mci->nr_csrows; i++) { | 711 | for (i = 0; i < mci->tot_dimms; i++) { |
943 | int nr_pages = 0; | 712 | struct dimm_info *dimm = &mci->dimms[i]; |
944 | 713 | /* Only expose populated DIMMs */ | |
945 | csrow = &mci->csrows[i]; | 714 | if (dimm->nr_pages == 0) |
946 | for (j = 0; j < csrow->nr_channels; j++) | 715 | continue; |
947 | nr_pages += csrow->channels[j].dimm->nr_pages; | 716 | #ifdef CONFIG_EDAC_DEBUG |
948 | 717 | debugf1("%s creating dimm%d, located at ", | |
949 | if (nr_pages > 0) { | 718 | __func__, i); |
950 | err = edac_create_csrow_object(mci, csrow, i); | 719 | if (edac_debug_level >= 1) { |
951 | if (err) { | 720 | int lay; |
952 | debugf1("%s() failure: create csrow %d obj\n", | 721 | for (lay = 0; lay < mci->n_layers; lay++) |
953 | __func__, i); | 722 | printk(KERN_CONT "%s %d ", |
954 | goto fail1; | 723 | edac_layer_name[mci->layers[lay].type], |
955 | } | 724 | dimm->location[lay]); |
725 | printk(KERN_CONT "\n"); | ||
956 | } | 726 | } |
727 | #endif | ||
957 | } | 728 | } |
958 | 729 | ||
730 | err = edac_create_csrow_objects(mci); | ||
731 | if (err < 0) | ||
732 | goto fail; | ||
733 | |||
959 | return 0; | 734 | return 0; |
960 | 735 | ||
961 | fail1: | 736 | fail: |
962 | for (i--; i >= 0; i--) { | 737 | for (i--; i >= 0; i--) { |
963 | int nr_pages = 0; | 738 | struct dimm_info *dimm = &mci->dimms[i]; |
964 | 739 | if (dimm->nr_pages == 0) | |
965 | csrow = &mci->csrows[i]; | 740 | continue; |
966 | for (j = 0; j < csrow->nr_channels; j++) | 741 | put_device(&dimm->dev); |
967 | nr_pages += csrow->channels[j].dimm->nr_pages; | 742 | device_del(&dimm->dev); |
968 | if (nr_pages > 0) | ||
969 | kobject_put(&mci->csrows[i].kobj); | ||
970 | } | 743 | } |
971 | 744 | put_device(&mci->dev); | |
972 | /* remove the mci instance's attributes, if any */ | 745 | device_del(&mci->dev); |
973 | edac_remove_mci_instance_attributes(mci, | 746 | bus_unregister(&mci->bus); |
974 | mci->mc_driver_sysfs_attributes, &mci->edac_mci_kobj, 0); | 747 | kfree(mci->bus.name); |
975 | |||
976 | /* remove the symlink */ | ||
977 | sysfs_remove_link(kobj_mci, EDAC_DEVICE_SYMLINK); | ||
978 | |||
979 | fail0: | ||
980 | return err; | 748 | return err; |
981 | } | 749 | } |
982 | 750 | ||
@@ -985,98 +753,70 @@ fail0: | |||
985 | */ | 753 | */ |
986 | void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) | 754 | void edac_remove_sysfs_mci_device(struct mem_ctl_info *mci) |
987 | { | 755 | { |
988 | struct csrow_info *csrow; | 756 | int i; |
989 | int i, j; | ||
990 | 757 | ||
991 | debugf0("%s()\n", __func__); | 758 | debugf0("%s()\n", __func__); |
992 | 759 | ||
993 | /* remove all csrow kobjects */ | 760 | edac_delete_csrow_objects(mci); |
994 | debugf4("%s() unregister this mci kobj\n", __func__); | ||
995 | for (i = 0; i < mci->nr_csrows; i++) { | ||
996 | int nr_pages = 0; | ||
997 | |||
998 | csrow = &mci->csrows[i]; | ||
999 | for (j = 0; j < csrow->nr_channels; j++) | ||
1000 | nr_pages += csrow->channels[j].dimm->nr_pages; | ||
1001 | if (nr_pages > 0) { | ||
1002 | debugf0("%s() unreg csrow-%d\n", __func__, i); | ||
1003 | kobject_put(&mci->csrows[i].kobj); | ||
1004 | } | ||
1005 | } | ||
1006 | 761 | ||
1007 | /* remove this mci instance's attribtes */ | 762 | for (i = 0; i < mci->tot_dimms; i++) { |
1008 | if (mci->mc_driver_sysfs_attributes) { | 763 | struct dimm_info *dimm = &mci->dimms[i]; |
1009 | debugf4("%s() unregister mci private attributes\n", __func__); | 764 | if (dimm->nr_pages == 0) |
1010 | edac_remove_mci_instance_attributes(mci, | 765 | continue; |
1011 | mci->mc_driver_sysfs_attributes, | 766 | debugf0("%s(): removing device %s\n", __func__, |
1012 | &mci->edac_mci_kobj, 0); | 767 | dev_name(&dimm->dev)); |
768 | put_device(&dimm->dev); | ||
769 | device_del(&dimm->dev); | ||
1013 | } | 770 | } |
1014 | |||
1015 | /* remove the symlink */ | ||
1016 | debugf4("%s() remove_link\n", __func__); | ||
1017 | sysfs_remove_link(&mci->edac_mci_kobj, EDAC_DEVICE_SYMLINK); | ||
1018 | |||
1019 | /* unregister this instance's kobject */ | ||
1020 | debugf4("%s() remove_mci_instance\n", __func__); | ||
1021 | kobject_put(&mci->edac_mci_kobj); | ||
1022 | } | 771 | } |
1023 | 772 | ||
773 | void edac_unregister_sysfs(struct mem_ctl_info *mci) | ||
774 | { | ||
775 | 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 | } | ||
1024 | 781 | ||
782 | static void mc_attr_release(struct device *device) | ||
783 | { | ||
784 | debugf1("Releasing device %s\n", dev_name(device)); | ||
785 | } | ||
1025 | 786 | ||
1026 | 787 | static struct device_type mc_attr_type = { | |
788 | .release = mc_attr_release, | ||
789 | }; | ||
1027 | /* | 790 | /* |
1028 | * edac_setup_sysfs_mc_kset(void) | 791 | * Init/exit code for the module. Basically, creates/removes /sys/class/rc |
1029 | * | ||
1030 | * Initialize the mc_kset for the 'mc' entry | ||
1031 | * This requires creating the top 'mc' directory with a kset | ||
1032 | * and its controls/attributes. | ||
1033 | * | ||
1034 | * To this 'mc' kset, instance 'mci' will be grouped as children. | ||
1035 | * | ||
1036 | * Return: 0 SUCCESS | ||
1037 | * !0 FAILURE error code | ||
1038 | */ | 792 | */ |
1039 | int edac_sysfs_setup_mc_kset(void) | 793 | int __init edac_mc_sysfs_init(void) |
1040 | { | 794 | { |
1041 | int err = -EINVAL; | ||
1042 | struct bus_type *edac_subsys; | 795 | struct bus_type *edac_subsys; |
1043 | 796 | int err; | |
1044 | debugf1("%s()\n", __func__); | ||
1045 | 797 | ||
1046 | /* get the /sys/devices/system/edac subsys reference */ | 798 | /* get the /sys/devices/system/edac subsys reference */ |
1047 | edac_subsys = edac_get_sysfs_subsys(); | 799 | edac_subsys = edac_get_sysfs_subsys(); |
1048 | if (edac_subsys == NULL) { | 800 | if (edac_subsys == NULL) { |
1049 | debugf1("%s() no edac_subsys error=%d\n", __func__, err); | 801 | debugf1("%s() no edac_subsys\n", __func__); |
1050 | goto fail_out; | 802 | return -EINVAL; |
1051 | } | 803 | } |
1052 | 804 | ||
1053 | /* Init the MC's kobject */ | 805 | mci_pdev.bus = edac_subsys; |
1054 | mc_kset = kset_create_and_add("mc", NULL, &edac_subsys->dev_root->kobj); | 806 | mci_pdev.type = &mc_attr_type; |
1055 | if (!mc_kset) { | 807 | device_initialize(&mci_pdev); |
1056 | err = -ENOMEM; | 808 | dev_set_name(&mci_pdev, "mc"); |
1057 | debugf1("%s() Failed to register '.../edac/mc'\n", __func__); | ||
1058 | goto fail_kset; | ||
1059 | } | ||
1060 | 809 | ||
1061 | debugf1("%s() Registered '.../edac/mc' kobject\n", __func__); | 810 | err = device_add(&mci_pdev); |
811 | if (err < 0) | ||
812 | return err; | ||
1062 | 813 | ||
1063 | return 0; | 814 | return 0; |
1064 | |||
1065 | fail_kset: | ||
1066 | edac_put_sysfs_subsys(); | ||
1067 | |||
1068 | fail_out: | ||
1069 | return err; | ||
1070 | } | 815 | } |
1071 | 816 | ||
1072 | /* | 817 | void __exit edac_mc_sysfs_exit(void) |
1073 | * edac_sysfs_teardown_mc_kset | ||
1074 | * | ||
1075 | * deconstruct the mc_ket for memory controllers | ||
1076 | */ | ||
1077 | void edac_sysfs_teardown_mc_kset(void) | ||
1078 | { | 818 | { |
1079 | kset_unregister(mc_kset); | 819 | put_device(&mci_pdev); |
820 | device_del(&mci_pdev); | ||
1080 | edac_put_sysfs_subsys(); | 821 | edac_put_sysfs_subsys(); |
1081 | } | 822 | } |
1082 | |||