*
* Written Doug Thompson <norsk5@xmission.com> www.softwarebitmaker.com
*
- * (c) 2012-2013 - Mauro Carvalho Chehab <mchehab@redhat.com>
+ * (c) 2012-2013 - Mauro Carvalho Chehab
* The entire API were re-written, and ported to use struct device
*
*/
static int edac_set_poll_msec(const char *val, struct kernel_param *kp)
{
- long l;
+ unsigned long l;
int ret;
if (!val)
return -EINVAL;
- ret = strict_strtol(val, 0, &l);
- if (ret == -EINVAL || ((int)l != l))
+ ret = kstrtoul(val, 0, &l);
+ if (ret)
+ return ret;
+
+ if (l < 1000)
return -EINVAL;
- *((int *)kp->arg) = l;
+
+ *((unsigned long *)kp->arg) = l;
/* notify edac_mc engine to reset the poll period */
edac_mc_reset_delay_period(l);
[MEM_RDDR2] = "Registered-DDR2",
[MEM_XDR] = "XDR",
[MEM_DDR3] = "Unbuffered-DDR3",
- [MEM_RDDR3] = "Registered-DDR3"
+ [MEM_RDDR3] = "Registered-DDR3",
+ [MEM_DDR4] = "Unbuffered-DDR4",
+ [MEM_RDDR4] = "Registered-DDR4"
};
static const char * const dev_types[] = {
};
#define DEVICE_CHANNEL(_name, _mode, _show, _store, _var) \
- struct dev_ch_attribute dev_attr_legacy_##_name = \
+ static struct dev_ch_attribute dev_attr_legacy_##_name = \
{ __ATTR(_name, _mode, _show, _store), (_var) }
#define to_channel(k) (container_of(k, struct dev_ch_attribute, attr)->channel)
if (!rank->dimm->label[0])
return 0;
- return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n",
+ return snprintf(data, sizeof(rank->dimm->label) + 1, "%s\n",
rank->dimm->label);
}
struct csrow_info *csrow = to_csrow(dev);
unsigned chan = to_channel(mattr);
struct rank_info *rank = csrow->channels[chan];
+ size_t copy_count = count;
+
+ if (count == 0)
+ return -EINVAL;
- ssize_t max_size = 0;
+ if (data[count - 1] == '\0' || data[count - 1] == '\n')
+ copy_count -= 1;
- max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1);
- strncpy(rank->dimm->label, data, max_size);
- rank->dimm->label[max_size] = '\0';
+ if (copy_count == 0 || copy_count >= sizeof(rank->dimm->label))
+ return -EINVAL;
+
+ strncpy(rank->dimm->label, data, copy_count);
+ rank->dimm->label[copy_count] = '\0';
- return max_size;
+ return count;
}
/* show function for dynamic chX_ce_count attribute */
*
*/
-#define EDAC_NR_CHANNELS 6
-
DEVICE_CHANNEL(ch0_dimm_label, S_IRUGO | S_IWUSR,
channel_dimm_label_show, channel_dimm_label_store, 0);
DEVICE_CHANNEL(ch1_dimm_label, S_IRUGO | S_IWUSR,
channel_dimm_label_show, channel_dimm_label_store, 5);
/* Total possible dynamic DIMM Label attribute file table */
-static struct device_attribute *dynamic_csrow_dimm_attr[] = {
- &dev_attr_legacy_ch0_dimm_label.attr,
- &dev_attr_legacy_ch1_dimm_label.attr,
- &dev_attr_legacy_ch2_dimm_label.attr,
- &dev_attr_legacy_ch3_dimm_label.attr,
- &dev_attr_legacy_ch4_dimm_label.attr,
- &dev_attr_legacy_ch5_dimm_label.attr
+static struct attribute *dynamic_csrow_dimm_attr[] = {
+ &dev_attr_legacy_ch0_dimm_label.attr.attr,
+ &dev_attr_legacy_ch1_dimm_label.attr.attr,
+ &dev_attr_legacy_ch2_dimm_label.attr.attr,
+ &dev_attr_legacy_ch3_dimm_label.attr.attr,
+ &dev_attr_legacy_ch4_dimm_label.attr.attr,
+ &dev_attr_legacy_ch5_dimm_label.attr.attr,
+ NULL
};
/* possible dynamic channel ce_count attribute files */
channel_ce_count_show, NULL, 5);
/* Total possible dynamic ce_count attribute file table */
-static struct device_attribute *dynamic_csrow_ce_count_attr[] = {
- &dev_attr_legacy_ch0_ce_count.attr,
- &dev_attr_legacy_ch1_ce_count.attr,
- &dev_attr_legacy_ch2_ce_count.attr,
- &dev_attr_legacy_ch3_ce_count.attr,
- &dev_attr_legacy_ch4_ce_count.attr,
- &dev_attr_legacy_ch5_ce_count.attr
+static struct attribute *dynamic_csrow_ce_count_attr[] = {
+ &dev_attr_legacy_ch0_ce_count.attr.attr,
+ &dev_attr_legacy_ch1_ce_count.attr.attr,
+ &dev_attr_legacy_ch2_ce_count.attr.attr,
+ &dev_attr_legacy_ch3_ce_count.attr.attr,
+ &dev_attr_legacy_ch4_ce_count.attr.attr,
+ &dev_attr_legacy_ch5_ce_count.attr.attr,
+ NULL
+};
+
+static umode_t csrow_dev_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct csrow_info *csrow = container_of(dev, struct csrow_info, dev);
+
+ if (idx >= csrow->nr_channels)
+ return 0;
+ /* Only expose populated DIMMs */
+ if (!csrow->channels[idx]->dimm->nr_pages)
+ return 0;
+ return attr->mode;
+}
+
+
+static const struct attribute_group csrow_dev_dimm_group = {
+ .attrs = dynamic_csrow_dimm_attr,
+ .is_visible = csrow_dev_is_visible,
+};
+
+static const struct attribute_group csrow_dev_ce_count_group = {
+ .attrs = dynamic_csrow_ce_count_attr,
+ .is_visible = csrow_dev_is_visible,
+};
+
+static const struct attribute_group *csrow_dev_groups[] = {
+ &csrow_dev_dimm_group,
+ &csrow_dev_ce_count_group,
+ NULL
};
static inline int nr_pages_per_csrow(struct csrow_info *csrow)
static int edac_create_csrow_object(struct mem_ctl_info *mci,
struct csrow_info *csrow, int index)
{
- int err, chan;
-
- if (csrow->nr_channels >= EDAC_NR_CHANNELS)
- return -ENODEV;
-
csrow->dev.type = &csrow_attr_type;
- csrow->dev.bus = &mci->bus;
+ csrow->dev.bus = mci->bus;
+ csrow->dev.groups = csrow_dev_groups;
device_initialize(&csrow->dev);
csrow->dev.parent = &mci->dev;
csrow->mci = mci;
edac_dbg(0, "creating (virtual) csrow node %s\n",
dev_name(&csrow->dev));
- err = device_add(&csrow->dev);
- if (err < 0)
- return err;
-
- for (chan = 0; chan < csrow->nr_channels; chan++) {
- /* Only expose populated DIMMs */
- if (!csrow->channels[chan]->dimm->nr_pages)
- continue;
- err = device_create_file(&csrow->dev,
- dynamic_csrow_dimm_attr[chan]);
- if (err < 0)
- goto error;
- err = device_create_file(&csrow->dev,
- dynamic_csrow_ce_count_attr[chan]);
- if (err < 0) {
- device_remove_file(&csrow->dev,
- dynamic_csrow_dimm_attr[chan]);
- goto error;
- }
- }
-
- return 0;
-
-error:
- for (--chan; chan >= 0; chan--) {
- device_remove_file(&csrow->dev,
- dynamic_csrow_dimm_attr[chan]);
- device_remove_file(&csrow->dev,
- dynamic_csrow_ce_count_attr[chan]);
- }
- put_device(&csrow->dev);
-
- return err;
+ return device_add(&csrow->dev);
}
/* Create a CSROW object under specifed edac_mc_device */
static int edac_create_csrow_objects(struct mem_ctl_info *mci)
{
- int err, i, chan;
+ int err, i;
struct csrow_info *csrow;
for (i = 0; i < mci->nr_csrows; i++) {
csrow = mci->csrows[i];
if (!nr_pages_per_csrow(csrow))
continue;
- for (chan = csrow->nr_channels - 1; chan >= 0; chan--) {
- if (!csrow->channels[chan]->dimm->nr_pages)
- continue;
- device_remove_file(&csrow->dev,
- dynamic_csrow_dimm_attr[chan]);
- device_remove_file(&csrow->dev,
- dynamic_csrow_ce_count_attr[chan]);
- }
put_device(&mci->csrows[i]->dev);
}
static void edac_delete_csrow_objects(struct mem_ctl_info *mci)
{
- int i, chan;
+ int i;
struct csrow_info *csrow;
for (i = mci->nr_csrows - 1; i >= 0; i--) {
csrow = mci->csrows[i];
if (!nr_pages_per_csrow(csrow))
continue;
- for (chan = csrow->nr_channels - 1; chan >= 0; chan--) {
- if (!csrow->channels[chan]->dimm->nr_pages)
- continue;
- edac_dbg(1, "Removing csrow %d channel %d sysfs nodes\n",
- i, chan);
- device_remove_file(&csrow->dev,
- dynamic_csrow_dimm_attr[chan]);
- device_remove_file(&csrow->dev,
- dynamic_csrow_ce_count_attr[chan]);
- }
device_unregister(&mci->csrows[i]->dev);
}
}
if (!dimm->label[0])
return 0;
- return snprintf(data, EDAC_MC_LABEL_LEN, "%s\n", dimm->label);
+ return snprintf(data, sizeof(dimm->label) + 1, "%s\n", dimm->label);
}
static ssize_t dimmdev_label_store(struct device *dev,
size_t count)
{
struct dimm_info *dimm = to_dimm(dev);
+ size_t copy_count = count;
+
+ if (count == 0)
+ return -EINVAL;
+
+ if (data[count - 1] == '\0' || data[count - 1] == '\n')
+ copy_count -= 1;
- ssize_t max_size = 0;
+ if (copy_count == 0 || copy_count >= sizeof(dimm->label))
+ return -EINVAL;
- max_size = min((ssize_t) count, (ssize_t) EDAC_MC_LABEL_LEN - 1);
- strncpy(dimm->label, data, max_size);
- dimm->label[max_size] = '\0';
+ strncpy(dimm->label, data, copy_count);
+ dimm->label[copy_count] = '\0';
- return max_size;
+ return count;
}
static ssize_t dimmdev_size_show(struct device *dev,
dimm->mci = mci;
dimm->dev.type = &dimm_attr_type;
- dimm->dev.bus = &mci->bus;
+ dimm->dev.bus = mci->bus;
device_initialize(&dimm->dev);
dimm->dev.parent = &mci->dev;
unsigned long bandwidth = 0;
int new_bw = 0;
- if (strict_strtoul(data, 10, &bandwidth) < 0)
+ if (kstrtoul(data, 10, &bandwidth) < 0)
return -EINVAL;
new_bw = mci->set_sdram_scrub_rate(mci, bandwidth);
return p - data;
}
-#ifdef CONFIG_EDAC_DEBUG
-static ssize_t edac_fake_inject_write(struct file *file,
- const char __user *data,
- size_t count, loff_t *ppos)
-{
- struct device *dev = file->private_data;
- struct mem_ctl_info *mci = to_mci(dev);
- static enum hw_event_mc_err_type type;
- u16 errcount = mci->fake_inject_count;
-
- if (!errcount)
- errcount = 1;
-
- type = mci->fake_inject_ue ? HW_EVENT_ERR_UNCORRECTED
- : HW_EVENT_ERR_CORRECTED;
-
- printk(KERN_DEBUG
- "Generating %d %s fake error%s to %d.%d.%d to test core handling. NOTE: this won't test the driver-specific decoding logic.\n",
- errcount,
- (type == HW_EVENT_ERR_UNCORRECTED) ? "UE" : "CE",
- errcount > 1 ? "s" : "",
- mci->fake_inject_layer[0],
- mci->fake_inject_layer[1],
- mci->fake_inject_layer[2]
- );
- edac_mc_handle_error(type, mci, errcount, 0, 0, 0,
- mci->fake_inject_layer[0],
- mci->fake_inject_layer[1],
- mci->fake_inject_layer[2],
- "FAKE ERROR", "for EDAC testing only");
-
- return count;
-}
-
-static const struct file_operations debug_fake_inject_fops = {
- .open = simple_open,
- .write = edac_fake_inject_write,
- .llseek = generic_file_llseek,
-};
-#endif
-
/* default Control file */
-DEVICE_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store);
+static DEVICE_ATTR(reset_counters, S_IWUSR, NULL, mci_reset_counters_store);
/* default Attribute files */
-DEVICE_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL);
-DEVICE_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL);
-DEVICE_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL);
-DEVICE_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL);
-DEVICE_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL);
-DEVICE_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL);
-DEVICE_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL);
-DEVICE_ATTR(max_location, S_IRUGO, mci_max_location_show, NULL);
+static DEVICE_ATTR(mc_name, S_IRUGO, mci_ctl_name_show, NULL);
+static DEVICE_ATTR(size_mb, S_IRUGO, mci_size_mb_show, NULL);
+static DEVICE_ATTR(seconds_since_reset, S_IRUGO, mci_seconds_show, NULL);
+static DEVICE_ATTR(ue_noinfo_count, S_IRUGO, mci_ue_noinfo_show, NULL);
+static DEVICE_ATTR(ce_noinfo_count, S_IRUGO, mci_ce_noinfo_show, NULL);
+static DEVICE_ATTR(ue_count, S_IRUGO, mci_ue_count_show, NULL);
+static DEVICE_ATTR(ce_count, S_IRUGO, mci_ce_count_show, NULL);
+static DEVICE_ATTR(max_location, S_IRUGO, mci_max_location_show, NULL);
/* memory scrubber attribute file */
-DEVICE_ATTR(sdram_scrub_rate, 0, NULL, NULL);
+DEVICE_ATTR(sdram_scrub_rate, 0, mci_sdram_scrub_rate_show,
+ mci_sdram_scrub_rate_store); /* umode set later in is_visible */
static struct attribute *mci_attrs[] = {
&dev_attr_reset_counters.attr,
&dev_attr_ue_count.attr,
&dev_attr_ce_count.attr,
&dev_attr_max_location.attr,
+ &dev_attr_sdram_scrub_rate.attr,
NULL
};
+static umode_t mci_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct mem_ctl_info *mci = to_mci(dev);
+ umode_t mode = 0;
+
+ if (attr != &dev_attr_sdram_scrub_rate.attr)
+ return attr->mode;
+ if (mci->get_sdram_scrub_rate)
+ mode |= S_IRUGO;
+ if (mci->set_sdram_scrub_rate)
+ mode |= S_IWUSR;
+ return mode;
+}
+
static struct attribute_group mci_attr_grp = {
.attrs = mci_attrs,
+ .is_visible = mci_attr_is_visible,
};
static const struct attribute_group *mci_attr_groups[] = {
.release = mci_attr_release,
};
-#ifdef CONFIG_EDAC_DEBUG
-static struct dentry *edac_debugfs;
-
-int __init edac_debugfs_init(void)
-{
- edac_debugfs = debugfs_create_dir("edac", NULL);
- if (IS_ERR(edac_debugfs)) {
- edac_debugfs = NULL;
- return -ENOMEM;
- }
- return 0;
-}
-
-void __exit edac_debugfs_exit(void)
-{
- debugfs_remove(edac_debugfs);
-}
-
-int edac_create_debug_nodes(struct mem_ctl_info *mci)
-{
- struct dentry *d, *parent;
- char name[80];
- int i;
-
- if (!edac_debugfs)
- return -ENODEV;
-
- d = debugfs_create_dir(mci->dev.kobj.name, edac_debugfs);
- if (!d)
- return -ENOMEM;
- parent = d;
-
- for (i = 0; i < mci->n_layers; i++) {
- sprintf(name, "fake_inject_%s",
- edac_layer_name[mci->layers[i].type]);
- d = debugfs_create_u8(name, S_IRUGO | S_IWUSR, parent,
- &mci->fake_inject_layer[i]);
- if (!d)
- goto nomem;
- }
-
- d = debugfs_create_bool("fake_inject_ue", S_IRUGO | S_IWUSR, parent,
- &mci->fake_inject_ue);
- if (!d)
- goto nomem;
-
- d = debugfs_create_u16("fake_inject_count", S_IRUGO | S_IWUSR, parent,
- &mci->fake_inject_count);
- if (!d)
- goto nomem;
-
- d = debugfs_create_file("fake_inject", S_IWUSR, parent,
- &mci->dev,
- &debug_fake_inject_fops);
- if (!d)
- goto nomem;
-
- mci->debugfs = parent;
- return 0;
-nomem:
- debugfs_remove(mci->debugfs);
- return -ENOMEM;
-}
-#endif
-
/*
* Create a new Memory Controller kobject instance,
* mc<id> under the 'mc' directory
* 0 Success
* !0 Failure
*/
-int edac_create_sysfs_mci_device(struct mem_ctl_info *mci)
+int edac_create_sysfs_mci_device(struct mem_ctl_info *mci,
+ const struct attribute_group **groups)
{
+ char *name;
int i, err;
/*
* The memory controller needs its own bus, in order to avoid
* namespace conflicts at /sys/bus/edac.
*/
- mci->bus.name = kasprintf(GFP_KERNEL, "mc%d", mci->mc_idx);
- if (!mci->bus.name)
+ name = kasprintf(GFP_KERNEL, "mc%d", mci->mc_idx);
+ if (!name)
return -ENOMEM;
- edac_dbg(0, "creating bus %s\n", mci->bus.name);
- err = bus_register(&mci->bus);
- if (err < 0)
+
+ mci->bus->name = name;
+
+ edac_dbg(0, "creating bus %s\n", mci->bus->name);
+
+ err = bus_register(mci->bus);
+ if (err < 0) {
+ kfree(name);
return err;
+ }
/* get the /sys/devices/system/edac subsys reference */
mci->dev.type = &mci_attr_type;
device_initialize(&mci->dev);
mci->dev.parent = mci_pdev;
- mci->dev.bus = &mci->bus;
+ mci->dev.bus = mci->bus;
+ mci->dev.groups = groups;
dev_set_name(&mci->dev, "mc%d", mci->mc_idx);
dev_set_drvdata(&mci->dev, mci);
pm_runtime_forbid(&mci->dev);
err = device_add(&mci->dev);
if (err < 0) {
edac_dbg(1, "failure: create device %s\n", dev_name(&mci->dev));
- bus_unregister(&mci->bus);
- kfree(mci->bus.name);
- return err;
+ goto fail_unregister_bus;
}
- if (mci->set_sdram_scrub_rate || mci->get_sdram_scrub_rate) {
- if (mci->get_sdram_scrub_rate) {
- dev_attr_sdram_scrub_rate.attr.mode |= S_IRUGO;
- dev_attr_sdram_scrub_rate.show = &mci_sdram_scrub_rate_show;
- }
- if (mci->set_sdram_scrub_rate) {
- dev_attr_sdram_scrub_rate.attr.mode |= S_IWUSR;
- dev_attr_sdram_scrub_rate.store = &mci_sdram_scrub_rate_store;
- }
- err = device_create_file(&mci->dev,
- &dev_attr_sdram_scrub_rate);
- if (err) {
- edac_dbg(1, "failure: create sdram_scrub_rate\n");
- goto fail2;
- }
- }
/*
* Create the dimm/rank devices
*/
for (i = 0; i < mci->tot_dimms; i++) {
struct dimm_info *dimm = mci->dimms[i];
/* Only expose populated DIMMs */
- if (dimm->nr_pages == 0)
+ if (!dimm->nr_pages)
continue;
+
#ifdef CONFIG_EDAC_DEBUG
edac_dbg(1, "creating dimm%d, located at ", i);
if (edac_debug_level >= 1) {
err = edac_create_dimm_object(mci, dimm, i);
if (err) {
edac_dbg(1, "failure: create dimm %d obj\n", i);
- goto fail;
+ goto fail_unregister_dimm;
}
}
#ifdef CONFIG_EDAC_LEGACY_SYSFS
err = edac_create_csrow_objects(mci);
if (err < 0)
- goto fail;
+ goto fail_unregister_dimm;
#endif
-#ifdef CONFIG_EDAC_DEBUG
- edac_create_debug_nodes(mci);
-#endif
+ edac_create_debugfs_nodes(mci);
return 0;
-fail:
+fail_unregister_dimm:
for (i--; i >= 0; i--) {
struct dimm_info *dimm = mci->dimms[i];
- if (dimm->nr_pages == 0)
+ if (!dimm->nr_pages)
continue;
+
device_unregister(&dimm->dev);
}
-fail2:
device_unregister(&mci->dev);
- bus_unregister(&mci->bus);
- kfree(mci->bus.name);
+fail_unregister_bus:
+ bus_unregister(mci->bus);
+ kfree(name);
+
return err;
}
edac_dbg(0, "\n");
#ifdef CONFIG_EDAC_DEBUG
- debugfs_remove(mci->debugfs);
+ edac_debugfs_remove_recursive(mci->debugfs);
#endif
#ifdef CONFIG_EDAC_LEGACY_SYSFS
edac_delete_csrow_objects(mci);
void edac_unregister_sysfs(struct mem_ctl_info *mci)
{
+ const char *name = mci->bus->name;
+
edac_dbg(1, "Unregistering device %s\n", dev_name(&mci->dev));
device_unregister(&mci->dev);
- bus_unregister(&mci->bus);
- kfree(mci->bus.name);
+ bus_unregister(mci->bus);
+ kfree(name);
}
static void mc_attr_release(struct device *dev)
return err;
}
-void __exit edac_mc_sysfs_exit(void)
+void edac_mc_sysfs_exit(void)
{
device_unregister(mci_pdev);
edac_put_sysfs_subsys();