@@ -464,6 +464,8 @@ EXPORT_SYMBOL_NS_GPL(cxl_mem_patrol_scrub_init, CXL);
#define CXL_MEMDEV_ECS_GET_FEAT_VERSION 0x01
#define CXL_MEMDEV_ECS_SET_FEAT_VERSION 0x01
+#define CXL_DDR5_ECS "cxl_ecs"
+
static const uuid_t cxl_ecs_uuid =
UUID_INIT(0xe5b13f22, 0x2328, 0x4a14, 0xb8, 0xba, 0xb9, 0x69, 0x1e, \
0x89, 0x33, 0x86);
@@ -582,9 +584,8 @@ static int cxl_mem_ecs_get_attrbs(struct device *dev, int fru_id,
return 0;
}
-static int __maybe_unused
-cxl_mem_ecs_set_attrbs(struct device *dev, int fru_id,
- struct cxl_memdev_ecs_params *params, u8 param_type)
+static int cxl_mem_ecs_set_attrbs(struct device *dev, int fru_id,
+ struct cxl_memdev_ecs_params *params, u8 param_type)
{
struct cxl_memdev_ecs_feat_read_attrbs *rd_attrbs __free(kvfree) = NULL;
struct cxl_memdev_ecs_set_feat_pi *set_pi __free(kvfree) = NULL;
@@ -731,10 +732,247 @@ cxl_mem_ecs_set_attrbs(struct device *dev, int fru_id,
return 0;
}
+static int cxl_mem_ecs_log_entry_type_write(struct device *dev, int region_id, long val)
+{
+ struct cxl_memdev_ecs_params params;
+ int ret;
+
+ params.log_entry_type = val;
+ ret = cxl_mem_ecs_set_attrbs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_LOG_ENTRY_TYPE);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for log entry type fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cxl_mem_ecs_threshold_write(struct device *dev, int region_id, long val)
+{
+ struct cxl_memdev_ecs_params params;
+ int ret;
+
+ params.threshold = val;
+ ret = cxl_mem_ecs_set_attrbs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_THRESHOLD);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for threshold fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cxl_mem_ecs_mode_write(struct device *dev, int region_id, long val)
+{
+ struct cxl_memdev_ecs_params params;
+ int ret;
+
+ params.mode = val;
+ ret = cxl_mem_ecs_set_attrbs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_MODE);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for mode fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cxl_mem_ecs_reset_counter_write(struct device *dev, int region_id, long val)
+{
+ struct cxl_memdev_ecs_params params;
+ int ret;
+
+ params.reset_counter = val;
+ ret = cxl_mem_ecs_set_attrbs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_RESET_COUNTER);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for reset ECC counter fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+enum cxl_mem_ecs_scrub_attributes {
+ cxl_ecs_log_entry_type,
+ cxl_ecs_log_entry_type_per_dram,
+ cxl_ecs_log_entry_type_per_memory_media,
+ cxl_ecs_mode,
+ cxl_ecs_mode_counts_codewords,
+ cxl_ecs_mode_counts_rows,
+ cxl_ecs_reset,
+ cxl_ecs_threshold,
+ cxl_ecs_threshold_available,
+ cxl_ecs_max_attrs,
+};
+
+static ssize_t cxl_mem_ecs_show_scrub_attr(struct device *dev, char *buf,
+ int attr_id)
+{
+ struct cxl_ecs_context *cxl_ecs_ctx = dev_get_drvdata(dev);
+ int region_id = cxl_ecs_ctx->region_id;
+ struct cxl_memdev_ecs_params params;
+ int ret;
+
+ if (attr_id == cxl_ecs_log_entry_type ||
+ attr_id == cxl_ecs_log_entry_type_per_dram ||
+ attr_id == cxl_ecs_log_entry_type_per_memory_media ||
+ attr_id == cxl_ecs_mode ||
+ attr_id == cxl_ecs_mode_counts_codewords ||
+ attr_id == cxl_ecs_mode_counts_rows ||
+ attr_id == cxl_ecs_threshold) {
+ ret = cxl_mem_ecs_get_attrbs(dev, region_id, ¶ms);
+ if (ret) {
+ dev_err(dev->parent, "Get CXL ECS params fail ret=%d\n", ret);
+ return ret;
+ }
+ }
+
+ switch (attr_id) {
+ case cxl_ecs_log_entry_type:
+ return sprintf(buf, "%d\n", params.log_entry_type);
+ case cxl_ecs_log_entry_type_per_dram:
+ if (params.log_entry_type == ECS_LOG_ENTRY_TYPE_DRAM)
+ return sysfs_emit(buf, "1\n");
+ else
+ return sysfs_emit(buf, "0\n");
+ case cxl_ecs_log_entry_type_per_memory_media:
+ if (params.log_entry_type == ECS_LOG_ENTRY_TYPE_MEM_MEDIA_FRU)
+ return sysfs_emit(buf, "1\n");
+ else
+ return sysfs_emit(buf, "0\n");
+ case cxl_ecs_mode:
+ return sprintf(buf, "%d\n", params.mode);
+ case cxl_ecs_mode_counts_codewords:
+ if (params.mode == ECS_MODE_COUNTS_CODEWORDS)
+ return sysfs_emit(buf, "1\n");
+ else
+ return sysfs_emit(buf, "0\n");
+ case cxl_ecs_mode_counts_rows:
+ if (params.mode == ECS_MODE_COUNTS_ROWS)
+ return sysfs_emit(buf, "1\n");
+ else
+ return sysfs_emit(buf, "0\n");
+ case cxl_ecs_threshold:
+ return sprintf(buf, "%d\n", params.threshold);
+ case cxl_ecs_threshold_available:
+ return sysfs_emit(buf, "256,1024,4096\n");
+ }
+
+ return -ENOTSUPP;
+}
+
+static ssize_t cxl_mem_ecs_store_scrub_attr(struct device *dev, const char *buf,
+ size_t count, int attr_id)
+{
+ struct cxl_ecs_context *cxl_ecs_ctx = dev_get_drvdata(dev);
+ int region_id = cxl_ecs_ctx->region_id;
+ long val;
+ int ret;
+
+ ret = kstrtol(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ switch (attr_id) {
+ case cxl_ecs_log_entry_type:
+ ret = cxl_mem_ecs_log_entry_type_write(dev, region_id, val);
+ if (ret)
+ return -ENOTSUPP;
+ break;
+ case cxl_ecs_mode:
+ ret = cxl_mem_ecs_mode_write(dev, region_id, val);
+ if (ret)
+ return -ENOTSUPP;
+ break;
+ case cxl_ecs_reset:
+ ret = cxl_mem_ecs_reset_counter_write(dev, region_id, val);
+ if (ret)
+ return -ENOTSUPP;
+ break;
+ case cxl_ecs_threshold:
+ ret = cxl_mem_ecs_threshold_write(dev, region_id, val);
+ if (ret)
+ return -ENOTSUPP;
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return count;
+}
+
+#define CXL_ECS_SCRUB_ATTR_RW(attrb) \
+static ssize_t attrb##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return cxl_mem_ecs_show_scrub_attr(dev, buf, (cxl_ecs_##attrb)); \
+} \
+static ssize_t attrb##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return cxl_mem_ecs_store_scrub_attr(dev, buf, count, (cxl_ecs_##attrb));\
+} \
+static DEVICE_ATTR_RW(attrb)
+
+#define CXL_ECS_SCRUB_ATTR_RO(attrb) \
+static ssize_t attrb##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return cxl_mem_ecs_show_scrub_attr(dev, buf, (cxl_ecs_##attrb)); \
+} \
+static DEVICE_ATTR_RO(attrb)
+
+#define CXL_ECS_SCRUB_ATTR_WO(attrb) \
+static ssize_t attrb##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return cxl_mem_ecs_store_scrub_attr(dev, buf, count, (cxl_ecs_##attrb));\
+} \
+static DEVICE_ATTR_WO(attrb)
+
+CXL_ECS_SCRUB_ATTR_RW(log_entry_type);
+CXL_ECS_SCRUB_ATTR_RO(log_entry_type_per_dram);
+CXL_ECS_SCRUB_ATTR_RO(log_entry_type_per_memory_media);
+CXL_ECS_SCRUB_ATTR_RW(mode);
+CXL_ECS_SCRUB_ATTR_RO(mode_counts_codewords);
+CXL_ECS_SCRUB_ATTR_RO(mode_counts_rows);
+CXL_ECS_SCRUB_ATTR_WO(reset);
+CXL_ECS_SCRUB_ATTR_RW(threshold);
+CXL_ECS_SCRUB_ATTR_RO(threshold_available);
+
+static struct attribute *cxl_mem_ecs_scrub_attrs[] = {
+ &dev_attr_log_entry_type.attr,
+ &dev_attr_log_entry_type_per_dram.attr,
+ &dev_attr_log_entry_type_per_memory_media.attr,
+ &dev_attr_mode.attr,
+ &dev_attr_mode_counts_codewords.attr,
+ &dev_attr_mode_counts_rows.attr,
+ &dev_attr_reset.attr,
+ &dev_attr_threshold.attr,
+ &dev_attr_threshold_available.attr,
+ NULL,
+};
+
+static struct attribute_group cxl_mem_ecs_attr_group = {
+ .attrs = cxl_mem_ecs_scrub_attrs,
+};
+
int cxl_mem_ecs_init(struct cxl_memdev *cxlmd, int region_id)
{
+ char scrub_name[CXL_MEMDEV_MAX_NAME_LENGTH];
struct cxl_mbox_supp_feat_entry feat_entry;
struct cxl_ecs_context *cxl_ecs_ctx;
+ struct device *cxl_scrub_dev;
int nmedia_frus;
int ret;
@@ -755,6 +993,15 @@ int cxl_mem_ecs_init(struct cxl_memdev *cxlmd, int region_id)
cxl_ecs_ctx->get_feat_size = feat_entry.get_feat_size;
cxl_ecs_ctx->set_feat_size = feat_entry.set_feat_size;
cxl_ecs_ctx->region_id = region_id;
+
+ snprintf(scrub_name, sizeof(scrub_name), "%s_%s_region%d",
+ CXL_DDR5_ECS, dev_name(&cxlmd->dev), cxl_ecs_ctx->region_id);
+ cxl_scrub_dev = devm_scrub_device_register(&cxlmd->dev, scrub_name,
+ cxl_ecs_ctx, NULL,
+ cxl_ecs_ctx->region_id,
+ &cxl_mem_ecs_attr_group);
+ if (IS_ERR(cxl_scrub_dev))
+ return PTR_ERR(cxl_scrub_dev);
}
return 0;