@@ -162,11 +162,17 @@ config CXL_SCRUB
bool "CXL: Memory scrub feature"
depends on CXL_PCI
depends on CXL_MEM
+ depends on SCRUB
help
The CXL memory scrub control is an optional feature allows host to
control the scrub configurations of CXL Type 3 devices, which
support patrol scrub and/or DDR5 ECS(Error Check Scrub).
+ Register with the scrub configure driver to expose sysfs attributes
+ to the user for configuring the CXL device memory patrol and DDR5 ECS
+ scrubs. Provides the interface functions to support configuring the
+ CXL memory device patrol and ECS scrubs.
+
Say 'y/n' to enable/disable the CXL memory scrub driver that will
attach to CXL.mem devices for memory scrub control feature. See
sections 8.2.9.9.11.1 and 8.2.9.9.11.2 in the CXL 3.1 specification
@@ -6,14 +6,19 @@
*
* - Provides functions to configure patrol scrub and DDR5 ECS features
* of the CXL memory devices.
+ * - Registers with the scrub subsystem driver to expose the sysfs attributes
+ * to the user for configuring the memory patrol scrub and DDR5 ECS features.
+
*/
#define pr_fmt(fmt) "CXL_MEM_SCRUB: " fmt
#include <cxlmem.h>
+#include <memory/memory-scrub.h>
/* CXL memory scrub feature common definitions */
#define CXL_SCRUB_MAX_ATTR_RANGE_LENGTH 128
+#define CXL_MEMDEV_MAX_NAME_LENGTH 128
static int cxl_mem_get_supported_feature_entry(struct cxl_memdev *cxlmd, const uuid_t *feat_uuid,
struct cxl_mbox_supp_feat_entry *feat_entry_out)
@@ -157,9 +162,8 @@ static int cxl_mem_ps_get_attrs(struct device *dev,
return 0;
}
-static int __maybe_unused
-cxl_mem_ps_set_attrs(struct device *dev, struct cxl_memdev_ps_params *params,
- u8 param_type)
+static int cxl_mem_ps_set_attrs(struct device *dev, struct cxl_memdev_ps_params *params,
+ u8 param_type)
{
struct cxl_memdev_ps_wr_attrs wr_attrs;
struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
@@ -215,11 +219,192 @@ cxl_mem_ps_set_attrs(struct device *dev, struct cxl_memdev_ps_params *params,
return 0;
}
+static int cxl_mem_ps_enable_read(struct device *dev, u64 *val)
+{
+ struct cxl_memdev_ps_params params;
+ int ret;
+
+ ret = cxl_mem_ps_get_attrs(dev, ¶ms);
+ if (ret) {
+ dev_err(dev, "Get CXL patrol scrub params failed ret=%d\n", ret);
+ return ret;
+ }
+ *val = params.enable;
+
+ return 0;
+}
+
+static int cxl_mem_ps_enable_write(struct device *dev, long val)
+{
+ struct cxl_memdev_ps_params params;
+ int ret;
+
+ params.enable = val;
+ ret = cxl_mem_ps_set_attrs(dev, ¶ms, CXL_MEMDEV_PS_PARAM_ENABLE);
+ if (ret) {
+ dev_err(dev, "CXL patrol scrub enable failed, enable=%d ret=%d\n",
+ params.enable, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cxl_mem_ps_rate_read(struct device *dev, u64 *val)
+{
+ struct cxl_memdev_ps_params params;
+ int ret;
+
+ ret = cxl_mem_ps_get_attrs(dev, ¶ms);
+ if (ret) {
+ dev_err(dev, "Get CXL patrol scrub params failed ret=%d\n", ret);
+ return ret;
+ }
+ *val = params.rate;
+
+ return 0;
+}
+
+static int cxl_mem_ps_rate_write(struct device *dev, long val)
+{
+ struct cxl_memdev_ps_params params;
+ int ret;
+
+ params.rate = val;
+ ret = cxl_mem_ps_set_attrs(dev, ¶ms, CXL_MEMDEV_PS_PARAM_RATE);
+ if (ret) {
+ dev_err(dev, "Set CXL patrol scrub params for rate failed ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cxl_mem_ps_rate_available_read(struct device *dev, char *buf)
+{
+ struct cxl_memdev_ps_params params;
+ int ret;
+
+ ret = cxl_mem_ps_get_attrs(dev, ¶ms);
+ if (ret) {
+ dev_err(dev, "Get CXL patrol scrub params failed ret=%d\n", ret);
+ return ret;
+ }
+
+ sysfs_emit(buf, "%s\n", params.rate_avail);
+
+ return 0;
+}
+
+/**
+ * cxl_mem_patrol_scrub_is_visible() - Callback to return attribute visibility
+ * @dev: Pointer to scrub device
+ * @attr: Scrub attribute
+ * @mode: attribute's mode
+ * @region_id: ID of the memory region
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+static umode_t cxl_mem_patrol_scrub_is_visible(struct device *dev, u32 attr_id,
+ umode_t mode, int region_id)
+{
+ const struct cxl_patrol_scrub_context *cxl_ps_ctx = dev_get_drvdata(dev);
+
+ if (attr_id == scrub_rate_available ||
+ attr_id == scrub_rate) {
+ if (!cxl_ps_ctx->scrub_cycle_changeable)
+ return 0;
+ }
+
+ switch (attr_id) {
+ case scrub_rate_available:
+ case scrub_enable:
+ case scrub_rate:
+ return mode;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * cxl_mem_patrol_scrub_read() - Read callback for data attributes
+ * @dev: Pointer to scrub device
+ * @attr: Scrub attribute
+ * @region_id: ID of the memory region
+ * @val: Pointer to the returned data
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+static int cxl_mem_patrol_scrub_read(struct device *dev, u32 attr,
+ int region_id, u64 *val)
+{
+
+ switch (attr) {
+ case scrub_enable:
+ return cxl_mem_ps_enable_read(dev->parent, val);
+ case scrub_rate:
+ return cxl_mem_ps_rate_read(dev->parent, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * cxl_mem_patrol_scrub_write() - Write callback for data attributes
+ * @dev: Pointer to scrub device
+ * @attr: Scrub attribute
+ * @region_id: ID of the memory region
+ * @val: Value to write
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+static int cxl_mem_patrol_scrub_write(struct device *dev, u32 attr,
+ int region_id, u64 val)
+{
+ switch (attr) {
+ case scrub_enable:
+ return cxl_mem_ps_enable_write(dev->parent, val);
+ case scrub_rate:
+ return cxl_mem_ps_rate_write(dev->parent, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * cxl_mem_patrol_scrub_read_strings() - Read callback for string attributes
+ * @dev: Pointer to scrub device
+ * @attr: Scrub attribute
+ * @region_id: ID of the memory region
+ * @buf: Pointer to the buffer for copying returned string
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+static int cxl_mem_patrol_scrub_read_strings(struct device *dev, u32 attr,
+ int region_id, char *buf)
+{
+ switch (attr) {
+ case scrub_rate_available:
+ return cxl_mem_ps_rate_available_read(dev->parent, buf);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct scrub_ops cxl_ps_scrub_ops = {
+ .is_visible = cxl_mem_patrol_scrub_is_visible,
+ .read = cxl_mem_patrol_scrub_read,
+ .write = cxl_mem_patrol_scrub_write,
+ .read_string = cxl_mem_patrol_scrub_read_strings,
+};
+
int cxl_mem_patrol_scrub_init(struct cxl_memdev *cxlmd)
{
+ char scrub_name[CXL_MEMDEV_MAX_NAME_LENGTH];
struct cxl_patrol_scrub_context *cxl_ps_ctx;
struct cxl_mbox_supp_feat_entry feat_entry;
struct cxl_memdev_ps_params params;
+ struct device *cxl_scrub_dev;
int ret;
ret = cxl_mem_get_supported_feature_entry(cxlmd, &cxl_patrol_scrub_uuid,
@@ -243,6 +428,14 @@ int cxl_mem_patrol_scrub_init(struct cxl_memdev *cxlmd)
cxl_ps_ctx->set_feat_size = feat_entry.set_size;
cxl_ps_ctx->scrub_cycle_changeable = params.scrub_cycle_changeable;
+ snprintf(scrub_name, sizeof(scrub_name), "%s_%s",
+ "cxl_patrol_scrub", dev_name(&cxlmd->dev));
+ cxl_scrub_dev = devm_scrub_device_register(&cxlmd->dev, scrub_name,
+ cxl_ps_ctx, &cxl_ps_scrub_ops,
+ 0, NULL);
+ if (IS_ERR(cxl_scrub_dev))
+ return PTR_ERR(cxl_scrub_dev);
+
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_patrol_scrub_init, CXL);