new file mode 100644
@@ -0,0 +1,79 @@
+See `Documentation/ABI/testing/sysfs-class-scrub-configure` for the
+documentation of common scrub configure directory layout (/sys/class/scrub/),
+including the attributes used for configuring the CXL patrol scrub.
+Following are the attributes defined for configuring the CXL ECS.
+
+What: /sys/class/scrub/scrubX/regionN/ecs_log_entry_type
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) The log entry type of how the DDR5 ECS log is
+ reported.
+ 00b - per DRAM.
+ 01b - per memory media FRU.
+
+What: /sys/class/scrub/scrubX/regionN/ecs_log_entry_type_per_dram
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Returns true if current log entry type of DDR5 ECS
+ region is per DRAM.
+
+What: /sys/class/scrub/scrubX/regionN/ecs_log_entry_type_per_memory_media
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Returns true if current log entry type of DDR5 ECS
+ region is per memory media FRU.
+
+What: /sys/class/scrub/scrubX/regionN/mode
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) The mode of how the DDR5 ECS counts the errors.
+ 0 - ECS counts rows with errors.
+ 1 - ECS counts codewords with errors.
+
+What: /sys/class/scrub/scrubX/regionN/mode_counts_rows
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Returns true if current mode of DDR5 ECS region
+ is counts rows with errors.
+
+What: /sys/class/scrub/scrubX/regionN/mode_counts_codewords
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Returns true if current mode of DDR5 ECS region
+ is counts codewords with errors.
+
+What: /sys/class/scrub/scrubX/regionN/reset_counter
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (WO) DDR5 ECS reset ECC counter.
+ 0 - normal, ECC counter running actively.
+ 1 - reset ECC counter to the default value.
+
+What: /sys/class/scrub/scrubX/regionN/threshold
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) DDR5 ECS threshold count per GB of memory cells.
+
+What: /sys/class/scrub/scrubX/regionN/threshold_available
+Date: February 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Supported list of DDR5 ECS threshold count per GB of
+ memory cells.
@@ -558,9 +558,9 @@ cxl_mem_ecs_get_attrs(struct device *scrub_dev, int fru_id,
return 0;
}
-static int __maybe_unused
-cxl_mem_ecs_set_attrs(struct device *scrub_dev, int fru_id,
- struct cxl_memdev_ecs_params *params, u8 param_type)
+static int cxl_mem_ecs_set_attrs(struct device *scrub_dev, int fru_id,
+ struct cxl_memdev_ecs_params *params,
+ u8 param_type)
{
struct cxl_memdev *cxlmd = to_cxl_memdev(scrub_dev->parent);
struct cxl_dev_state *cxlds = cxlmd->cxlds;
@@ -677,8 +677,243 @@ cxl_mem_ecs_set_attrs(struct device *scrub_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_attrs(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 failed 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_attrs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_THRESHOLD);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for threshold failed 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_attrs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_MODE);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for mode failed 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_attrs(dev, region_id, ¶ms,
+ CXL_MEMDEV_ECS_PARAM_RESET_COUNTER);
+ if (ret) {
+ dev_err(dev->parent, "Set CXL ECS params for reset ECC counter failed 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_attrs(dev, region_id, ¶ms);
+ if (ret) {
+ dev_err(dev->parent, "Get CXL ECS params failed 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 -EOPNOTSUPP;
+}
+
+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 -EOPNOTSUPP;
+ break;
+ case cxl_ecs_mode:
+ ret = cxl_mem_ecs_mode_write(dev, region_id, val);
+ if (ret)
+ return -EOPNOTSUPP;
+ break;
+ case cxl_ecs_reset:
+ ret = cxl_mem_ecs_reset_counter_write(dev, region_id, val);
+ if (ret)
+ return -EOPNOTSUPP;
+ break;
+ case cxl_ecs_threshold:
+ ret = cxl_mem_ecs_threshold_write(dev, region_id, val);
+ if (ret)
+ return -EOPNOTSUPP;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return count;
+}
+
+#define CXL_ECS_SCRUB_ATTR_RW(attr) \
+static ssize_t attr##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return cxl_mem_ecs_show_scrub_attr(dev, buf, (cxl_ecs_##attr)); \
+} \
+static ssize_t attr##_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_##attr));\
+} \
+static DEVICE_ATTR_RW(attr)
+
+#define CXL_ECS_SCRUB_ATTR_RO(attr) \
+static ssize_t attr##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return cxl_mem_ecs_show_scrub_attr(dev, buf, (cxl_ecs_##attr)); \
+} \
+static DEVICE_ATTR_RO(attr)
+
+#define CXL_ECS_SCRUB_ATTR_WO(attr) \
+static ssize_t attr##_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_##attr));\
+} \
+static DEVICE_ATTR_WO(attr)
+
+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;
int nr_media_frus;
@@ -704,6 +939,16 @@ int cxl_mem_ecs_init(struct cxl_memdev *cxlmd, int region_id)
cxl_ecs_ctx->set_feat_size = feat_entry.set_size;
cxl_ecs_ctx->region_id = region_id;
+ snprintf(scrub_name, sizeof(scrub_name), "%s_%s_region%d",
+ "cxl_ecs", dev_name(&cxlmd->dev), cxl_ecs_ctx->region_id);
+ struct device *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;
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_ecs_init, CXL);