new file mode 100644
@@ -0,0 +1,69 @@
+What: /sys/bus/edac/devices/<dev-name>/ppr*
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ The sysfs EDAC bus devices /<dev-name>/ppr* subdirectory
+ belongs to the memory media PPR (Post Package Repair) control
+ feature, where <dev-name> directory corresponds to a device
+ registered with the EDAC PPR driver and thus registered with
+ the generic EDAC device driver.
+ /ppr* belongs to either sPPR (Soft PPR) or hPPR (Hard PPR)
+ feature for the memory device.
+ The sysfs PPR attr nodes would be only present if PPR is
+ supported.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/persist_mode_avail
+Date: Oct 2024
+KernelVersion: 6.12For e.g. HBM, DDR.
+Contact: linux-edac@vger.kernel.org
+Description:
+ (RO) persist PPR modes supported in the device.
+ For e.g. Hard PPR(hPPR) for a permanent row repair,
+ Soft PPR(sPPR) for a temporary row repair.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/persist_mode
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ (RW) Current persist PPR mode.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/dpa_support
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ (RO) True if supports DPA for PPR maintenance operation.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/ppr_safe_when_in_use
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ (RO) True if memory media is accessible and data is retained
+ during the PPR operation.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/repair_hpa
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ (WO) Start the PPR operation for the HPA (host physical
+ address) set. Return failure if resources are not available
+ to perform repair.
+
+What: /sys/bus/edac/devices/<dev-name>/ppr*/repair_dpa
+Date: Oct 2024
+KernelVersion: 6.12
+Contact: linux-edac@vger.kernel.org
+Description:
+ (WO) Starts the PPR operation for the DPA(device physical
+ address) set. Return failure if resources are not available
+ to perform repair.
+ In some states of system configuration (e.g. before address decoders
+ have been configured), memory devices (e.g. CXL) may not have an
+ active mapping in the main host address physical address map.
+ As such, the memory to repair must be identified by a device
+ specific physical addressing scheme using a DPA. The DPA to use
+ will be presented in related error records.
@@ -10,7 +10,7 @@ obj-$(CONFIG_EDAC) := edac_core.o
edac_core-y := edac_mc.o edac_device.o edac_mc_sysfs.o
edac_core-y += edac_module.o edac_device_sysfs.o wq.o
-edac_core-y += edac_scrub.o edac_ecs.o
+edac_core-y += edac_scrub.o edac_ecs.o edac_ppr.o
edac_core-$(CONFIG_EDAC_DEBUG) += debugfs.o
@@ -630,6 +630,10 @@ static int edac_dev_feat_init(struct device *parent,
case RAS_FEAT_PPR:
dev_data->ppr_ops = ras_feat->ppr_ops;
dev_data->private = ras_feat->ctx;
+ ret = edac_ppr_get_desc(parent, attr_groups,
+ ras_feat->instance);
+ if (ret)
+ return ret;
return 1;
default:
return -EINVAL;
new file mode 100755
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic EDAC PPR driver supports controlling the memory
+ * device with Post Package Repair (PPR) feature in the system
+ * and the common sysfs PPR control interface promotes unambiguous
+ * access from the userspace.
+ *
+ * Copyright (c) 2024 HiSilicon Limited.
+ */
+
+#define pr_fmt(fmt) "EDAC PPR: " fmt
+
+#include <linux/edac.h>
+
+enum edac_ppr_attributes {
+ PPR_PERSIST_MODE_AVAIL,
+ PPR_PERSIST_MODE,
+ PPR_DPA_SUPPORT,
+ PPR_SAFE_IN_USE,
+ PPR_HPA,
+ PPR_DPA,
+ PPR_MAX_ATTRS
+};
+
+struct edac_ppr_dev_attr {
+ struct device_attribute dev_attr;
+ u8 instance;
+};
+
+struct edac_ppr_context {
+ char name[EDAC_FEAT_NAME_LEN];
+ struct edac_ppr_dev_attr ppr_dev_attr[PPR_MAX_ATTRS];
+ struct attribute *ppr_attrs[PPR_MAX_ATTRS + 1];
+ struct attribute_group group;
+};
+
+#define to_ppr_dev_attr(_dev_attr) \
+ container_of(_dev_attr, struct edac_ppr_dev_attr, dev_attr)
+
+static ssize_t persist_mode_avail_show(struct device *ras_feat_dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+
+ return ops->get_persist_mode_avail(ras_feat_dev->parent,
+ ctx->ppr[inst].private, buf);
+}
+
+static ssize_t persist_mode_show(struct device *ras_feat_dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ u32 mode;
+ int ret;
+
+ ret = ops->get_persist_mode(ras_feat_dev->parent, ctx->ppr[inst].private, &mode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", mode);
+}
+
+static ssize_t persist_mode_store(struct device *ras_feat_dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ long mode;
+ int ret;
+
+ ret = kstrtol(buf, 0, &mode);
+ if (ret < 0)
+ return ret;
+
+ ret = ops->set_persist_mode(ras_feat_dev->parent, ctx->ppr[inst].private, mode);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t dpa_support_show(struct device *ras_feat_dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ int ret;
+ u32 val;
+
+ ret = ops->get_dpa_support(ras_feat_dev->parent, ctx->ppr[inst].private, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t ppr_safe_when_in_use_show(struct device *ras_feat_dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ int ret;
+ u32 val;
+
+ ret = ops->get_ppr_safe_when_in_use(ras_feat_dev->parent,
+ ctx->ppr[inst].private, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t repair_hpa_store(struct device *ras_feat_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ u64 hpa;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &hpa);
+ if (ret < 0)
+ return ret;
+
+ ret = ops->do_ppr(ras_feat_dev->parent, ctx->ppr[inst].private, true, hpa);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t repair_dpa_store(struct device *ras_feat_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+ u64 dpa;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &dpa);
+ if (ret < 0)
+ return ret;
+
+ ret = ops->do_ppr(ras_feat_dev->parent, ctx->ppr[inst].private, 0, dpa);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static umode_t ppr_attr_visible(struct kobject *kobj,
+ struct attribute *a, int attr_id)
+{
+ struct device *ras_feat_dev = kobj_to_dev(kobj);
+ struct device_attribute *dev_attr =
+ container_of(a, struct device_attribute, attr);
+ u8 inst = ((struct edac_ppr_dev_attr *)to_ppr_dev_attr(dev_attr))->instance;
+ struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
+ const struct edac_ppr_ops *ops = ctx->ppr[inst].ppr_ops;
+
+ switch (attr_id) {
+ case PPR_PERSIST_MODE_AVAIL:
+ return ops->get_persist_mode_avail ? a->mode : 0;
+ case PPR_PERSIST_MODE:
+ if (ops->get_persist_mode && ops->set_persist_mode)
+ return a->mode;
+ if (ops->get_persist_mode)
+ return 0444;
+ return 0;
+ case PPR_DPA_SUPPORT:
+ return ops->get_dpa_support ? a->mode : 0;
+ case PPR_SAFE_IN_USE:
+ return ops->get_ppr_safe_when_in_use ? a->mode : 0;
+ case PPR_HPA:
+ case PPR_DPA:
+ return ops->do_ppr ? a->mode : 0;
+ default:
+ return 0;
+ }
+}
+
+#define EDAC_PPR_ATTR_RO(_name, _instance) \
+ ((struct edac_ppr_dev_attr) { .dev_attr = __ATTR_RO(_name), \
+ .instance = _instance })
+
+#define EDAC_PPR_ATTR_WO(_name, _instance) \
+ ((struct edac_ppr_dev_attr) { .dev_attr = __ATTR_WO(_name), \
+ .instance = _instance })
+
+#define EDAC_PPR_ATTR_RW(_name, _instance) \
+ ((struct edac_ppr_dev_attr) { .dev_attr = __ATTR_RW(_name), \
+ .instance = _instance })
+
+static int ppr_create_desc(struct device *ppr_dev,
+ const struct attribute_group **attr_groups,
+ u8 instance)
+{
+ struct edac_ppr_context *ppr_ctx;
+ struct attribute_group *group;
+ int i;
+
+ ppr_ctx = devm_kzalloc(ppr_dev, sizeof(*ppr_ctx), GFP_KERNEL);
+ if (!ppr_ctx)
+ return -ENOMEM;
+
+ group = &ppr_ctx->group;
+ ppr_ctx->ppr_dev_attr[0] = EDAC_PPR_ATTR_RO(persist_mode_avail, instance);
+ ppr_ctx->ppr_dev_attr[1] = EDAC_PPR_ATTR_RW(persist_mode, instance);
+ ppr_ctx->ppr_dev_attr[2] = EDAC_PPR_ATTR_RO(dpa_support, instance);
+ ppr_ctx->ppr_dev_attr[3] = EDAC_PPR_ATTR_RO(ppr_safe_when_in_use, instance);
+ ppr_ctx->ppr_dev_attr[4] = EDAC_PPR_ATTR_WO(repair_hpa, instance);
+ ppr_ctx->ppr_dev_attr[5] = EDAC_PPR_ATTR_WO(repair_dpa, instance);
+ for (i = 0; i < PPR_MAX_ATTRS; i++)
+ ppr_ctx->ppr_attrs[i] = &ppr_ctx->ppr_dev_attr[i].dev_attr.attr;
+
+ sprintf(ppr_ctx->name, "%s%d", "ppr", instance);
+ group->name = ppr_ctx->name;
+ group->attrs = ppr_ctx->ppr_attrs;
+ group->is_visible = ppr_attr_visible;
+
+ attr_groups[0] = group;
+
+ return 0;
+}
+
+/**
+ * edac_ppr_get_desc - get EDAC PPR descriptors
+ * @ppr_dev: client PPR device
+ * @attr_groups: pointer to attrribute group container
+ * @instance: device's PPR instance number.
+ *
+ * Returns 0 on success, error otherwise.
+ */
+int edac_ppr_get_desc(struct device *ppr_dev,
+ const struct attribute_group **attr_groups,
+ u8 instance)
+{
+ if (!ppr_dev || !attr_groups)
+ return -EINVAL;
+
+ return ppr_create_desc(ppr_dev, attr_groups, instance);
+}
@@ -741,6 +741,36 @@ struct edac_ecs_ex_info {
int edac_ecs_get_desc(struct device *ecs_dev,
const struct attribute_group **attr_groups,
u16 num_media_frus);
+
+enum edac_ppr_type {
+ EDAC_TYPE_SPPR, /* soft PPR */
+ EDAC_TYPE_HPPR, /* hard PPR */
+};
+
+/**
+ * struct edac_ppr_ops - PPR(Post Package Repair) device operations
+ * (all elements optional)
+ * @get_persist_mode_avail: get the persist modes supported in the device.
+ * @get_persist_mode: get the persist mode of the PPR instance.
+ * @set_persist_mode: set the persist mode for the PPR instance.
+ * @get_dpa_support: get dpa support flag.
+ * @get_ppr_safe_when_in_use: get whether memory media is accessible and
+ * data is retained during PPR operation.
+ * @do_ppr: start PPR operation for the HPA/DPA set.
+ */
+struct edac_ppr_ops {
+ int (*get_persist_mode_avail)(struct device *dev, void *drv_data, char *buf);
+ int (*get_persist_mode)(struct device *dev, void *drv_data, u32 *mode);
+ int (*set_persist_mode)(struct device *dev, void *drv_data, u32 mode);
+ int (*get_dpa_support)(struct device *dev, void *drv_data, u32 *val);
+ int (*get_ppr_safe_when_in_use)(struct device *dev, void *drv_data, u32 *val);
+ int (*do_ppr)(struct device *dev, void *drv_data, bool hpa, u64 pa);
+};
+
+int edac_ppr_get_desc(struct device *ppr_dev,
+ const struct attribute_group **attr_groups,
+ u8 instance);
+
/*
* EDAC device feature information structure
*/