new file mode 100644
@@ -0,0 +1,91 @@
+What: /sys/class/scrub/
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ The scrub/ class subdirectory belongs to the
+ scrubber subsystem.
+
+What: /sys/class/scrub/scrubX/
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ The /sys/class/scrub/scrub{0,1,2,3,...} directories
+ correspond to each scrub device.
+
+What: /sys/class/scrub/scrubX/name
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) name of the memory scrub device
+
+What: /sys/class/scrub/scrubX/regionN/
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ The /sys/class/scrub/scrubX/region{0,1,2,3,...}
+ directories correspond to each scrub region under a scrub device.
+ Scrub region is a physical address range for which scrub may be
+ separately controlled. Regions may overlap in which case the
+ scrubbing rate of the overlapped memory will be at least that
+ expected due to each overlapping region.
+
+What: /sys/class/scrub/scrubX/regionN/addr_base
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) The base of the address range of the memory region
+ to be scrubbed.
+ On reading, returns the base of the memory region for
+ the actual address range(The platform calculates
+ the nearest patrol scrub boundary address from where
+ it can start scrub).
+
+What: /sys/class/scrub/scrubX/regionN/addr_size
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) The size of the address range to be scrubbed.
+ On reading, returns the size of the memory region for
+ the actual address range.
+
+What: /sys/class/scrub/scrubX/regionN/enable
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (WO) Enable/Disable scrub the memory region.
+ 1 - enable the memory scrub.
+ 0 - disable the memory scrub.
+
+What: /sys/class/scrub/scrubX/regionN/enable_background_scrub
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (WO) Enable/Disable background scrubbing if supported.
+ 1 - enable background scrubbing.
+ 0 - disable background scrubbing.
+
+What: /sys/class/scrub/scrubX/regionN/rate_available
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RO) Supported range for the scrub rate)
+ by the scrubber for a memory region.
+ The unit of the scrub rate vary depends on the scrub.
+
+What: /sys/class/scrub/scrubX/regionN/rate
+Date: January 2024
+KernelVersion: 6.8
+Contact: linux-kernel@vger.kernel.org
+Description:
+ (RW) The scrub rate in the memory region specified
+ and it must be with in the supported range by the scrub.
+ The unit of the scrub rate vary depends on the scrub.
@@ -227,5 +227,6 @@ config STM32_FMC2_EBI
source "drivers/memory/samsung/Kconfig"
source "drivers/memory/tegra/Kconfig"
+source "drivers/memory/scrub/Kconfig"
endif
@@ -27,6 +27,7 @@ obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o
obj-$(CONFIG_SAMSUNG_MC) += samsung/
obj-$(CONFIG_TEGRA_MC) += tegra/
+obj-$(CONFIG_SCRUB) += scrub/
obj-$(CONFIG_TI_EMIF_SRAM) += ti-emif-sram.o
obj-$(CONFIG_FPGA_DFL_EMIF) += dfl-emif.o
new file mode 100644
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Memory scrub driver configurations
+#
+
+config SCRUB
+ bool "Memory scrub driver"
+ help
+ This option selects the memory scrub subsystem, supports
+ configuring the parameters of underlying scrubbers in the
+ system for the DRAM memories.
new file mode 100644
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for memory scrub drivers
+#
+
+obj-$(CONFIG_SCRUB) += memory-scrub.o
new file mode 100755
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Memory scrub driver supports configuring
+ * the memory scrubs.
+ *
+ * Copyright (c) 2023 HiSilicon Limited.
+ */
+
+#define pr_fmt(fmt) "MEM SCRUB: " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+#include <memory/memory-scrub.h>
+
+/* memory scrubber config definitions */
+#define SCRUB_ID_PREFIX "scrub"
+#define SCRUB_ID_FORMAT SCRUB_ID_PREFIX "%d"
+#define SCRUB_DEV_MAX_NAME_LENGTH 128
+#define SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH 64
+
+static DEFINE_IDA(scrub_ida);
+
+struct scrub_device {
+ char name[SCRUB_DEV_MAX_NAME_LENGTH];
+ int id;
+ struct device dev;
+ char region_name[SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH];
+ int region_id;
+ struct attribute_group group;
+ const struct attribute_group *groups[2];
+ const struct scrub_ops *ops;
+};
+
+#define to_scrub_device(d) container_of(d, struct scrub_device, dev)
+
+static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", to_scrub_device(dev)->name);
+}
+static DEVICE_ATTR_RO(name);
+
+static struct attribute *scrub_dev_attrs[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static umode_t scrub_dev_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ if (attr != &dev_attr_name.attr)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group scrub_dev_attr_group = {
+ .attrs = scrub_dev_attrs,
+ .is_visible = scrub_dev_attr_is_visible,
+};
+
+static const struct attribute_group *scrub_dev_attr_groups[] = {
+ &scrub_dev_attr_group,
+ NULL
+};
+
+static void scrub_dev_release(struct device *dev)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+
+ ida_free(&scrub_ida, scrub_dev->id);
+ kfree(scrub_dev);
+}
+
+static struct class scrub_class = {
+ .name = "scrub",
+ .dev_groups = scrub_dev_attr_groups,
+ .dev_release = scrub_dev_release,
+};
+
+static umode_t scrub_attr_visible(struct kobject *kobj,
+ struct attribute *a, int attr_id)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+
+ if (!scrub_dev->ops)
+ return 0;
+
+ return scrub_dev->ops->is_visible(dev, attr_id, region_id);
+}
+
+static ssize_t scrub_attr_show(struct device *dev, int attr_id,
+ char *buf)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+ int ret;
+ u64 val;
+
+ ret = scrub_dev->ops->read(dev, attr_id, region_id, &val);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%lld\n", val);
+}
+
+static ssize_t scrub_attr_show_hex(struct device *dev, int attr_id,
+ char *buf)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+ int ret;
+ u64 val;
+
+ ret = scrub_dev->ops->read(dev, attr_id, region_id, &val);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "0x%llx\n", val);
+}
+
+static ssize_t scrub_attr_show_string(struct device *dev, int attr_id,
+ char *buf)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+ int ret;
+
+ ret = scrub_dev->ops->read_string(dev, attr_id, region_id, buf);
+ if (ret < 0)
+ return ret;
+
+ return strlen(buf);
+}
+
+static ssize_t scrub_attr_store(struct device *dev, int attr_id,
+ const char *buf, size_t count)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+ long val;
+ int ret;
+
+ ret = kstrtol(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ ret = scrub_dev->ops->write(dev, attr_id, region_id, val);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t scrub_attr_store_hex(struct device *dev, int attr_id,
+ const char *buf, size_t count)
+{
+ struct scrub_device *scrub_dev = to_scrub_device(dev);
+ int region_id = scrub_dev->region_id;
+ int ret;
+ u64 val;
+
+ ret = kstrtou64(buf, 16, &val);
+ if (ret < 0)
+ return ret;
+
+ ret = scrub_dev->ops->write(dev, attr_id, region_id, val);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t show_scrub_attr(struct device *dev, char *buf, int attr_id)
+{
+ switch (attr_id) {
+ case scrub_addr_base:
+ case scrub_addr_size:
+ return scrub_attr_show_hex(dev, attr_id, buf);
+ case scrub_enable:
+ case scrub_rate:
+ return scrub_attr_show(dev, attr_id, buf);
+ case scrub_rate_available:
+ return scrub_attr_show_string(dev, attr_id, buf);
+ }
+
+ return -ENOTSUPP;
+}
+
+static ssize_t store_scrub_attr(struct device *dev, const char *buf,
+ size_t count, int attr_id)
+{
+ switch (attr_id) {
+ case scrub_addr_base:
+ case scrub_addr_size:
+ return scrub_attr_store_hex(dev, attr_id, buf, count);
+ case scrub_enable:
+ case scrub_enable_background_scrub:
+ case scrub_rate:
+ return scrub_attr_store(dev, attr_id, buf, count);
+ }
+
+ return -ENOTSUPP;
+}
+
+#define SCRUB_ATTR_RW(attrb) \
+static ssize_t attrb##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return show_scrub_attr(dev, buf, (scrub_##attrb)); \
+} \
+static ssize_t attrb##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return store_scrub_attr(dev, buf, count, (scrub_##attrb));\
+} \
+static DEVICE_ATTR_RW(attrb)
+
+#define SCRUB_ATTR_RO(attrb) \
+static ssize_t attrb##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return show_scrub_attr(dev, buf, (scrub_##attrb)); \
+} \
+static DEVICE_ATTR_RO(attrb)
+
+#define SCRUB_ATTR_WO(attrb) \
+static ssize_t attrb##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return store_scrub_attr(dev, buf, count, (scrub_##attrb));\
+} \
+static DEVICE_ATTR_WO(attrb)
+
+SCRUB_ATTR_RW(addr_base);
+SCRUB_ATTR_RW(addr_size);
+SCRUB_ATTR_RW(enable);
+SCRUB_ATTR_RW(enable_background_scrub);
+SCRUB_ATTR_RW(rate);
+SCRUB_ATTR_RO(rate_available);
+
+static struct attribute *scrub_attrs[] = {
+ &dev_attr_addr_base.attr,
+ &dev_attr_addr_size.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_enable_background_scrub.attr,
+ &dev_attr_rate.attr,
+ &dev_attr_rate_available.attr,
+ NULL,
+};
+
+static struct device *
+scrub_device_register(struct device *dev, const char *name, void *drvdata,
+ const struct scrub_ops *ops,
+ int region_id,
+ struct attribute_group *attr_group)
+{
+ struct scrub_device *scrub_dev;
+ struct device *hdev;
+ int err;
+
+ scrub_dev = kzalloc(sizeof(*scrub_dev), GFP_KERNEL);
+ if (!scrub_dev)
+ return ERR_PTR(-ENOMEM);
+ hdev = &scrub_dev->dev;
+
+ scrub_dev->id = ida_alloc(&scrub_ida, GFP_KERNEL);
+ if (scrub_dev->id < 0) {
+ kfree(scrub_dev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ snprintf((char *)scrub_dev->region_name, SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH,
+ "region%d", region_id);
+ if (attr_group) {
+ attr_group->name = (char *)scrub_dev->region_name;
+ scrub_dev->groups[0] = attr_group;
+ scrub_dev->region_id = region_id;
+ } else {
+ scrub_dev->group.name = (char *)scrub_dev->region_name;
+ scrub_dev->group.attrs = scrub_attrs;
+ scrub_dev->group.is_visible = scrub_attr_visible;
+ scrub_dev->groups[0] = &scrub_dev->group;
+ scrub_dev->ops = ops;
+ scrub_dev->region_id = region_id;
+ }
+
+ hdev->groups = scrub_dev->groups;
+ hdev->class = &scrub_class;
+ hdev->parent = dev;
+ dev_set_drvdata(hdev, drvdata);
+ dev_set_name(hdev, SCRUB_ID_FORMAT, scrub_dev->id);
+ snprintf(scrub_dev->name, SCRUB_DEV_MAX_NAME_LENGTH, "%s", name);
+ err = device_register(hdev);
+ if (err) {
+ put_device(hdev);
+ return ERR_PTR(err);
+ }
+
+ return hdev;
+}
+
+static void devm_scrub_release(void *dev)
+{
+ struct device *hdev = dev;
+
+ device_unregister(hdev);
+}
+
+/**
+ * devm_scrub_device_register - register hw scrubber device
+ * @dev: the parent device
+ * @name: hw scrubber name attribute
+ * @drvdata: driver data to attach to created device
+ * @ops: pointer to scrub_ops structure (optional)
+ * @region_id: region ID
+ * @attr_group: input attribute group (optional)
+ *
+ * Returns the pointer to the new device. The new device is automatically
+ * unregistered with the parent device.
+ */
+struct device *
+devm_scrub_device_register(struct device *dev, const char *name,
+ void *drvdata,
+ const struct scrub_ops *ops,
+ int region_id,
+ struct attribute_group *attr_group)
+{
+ struct device *hdev;
+ int ret;
+
+ if (!dev || !name)
+ return ERR_PTR(-EINVAL);
+
+ hdev = scrub_device_register(dev, name, drvdata, ops,
+ region_id, attr_group);
+ if (IS_ERR(hdev))
+ return hdev;
+
+ ret = devm_add_action_or_reset(dev, devm_scrub_release, hdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return hdev;
+}
+EXPORT_SYMBOL_GPL(devm_scrub_device_register);
+
+static int __init memory_scrub_control_init(void)
+{
+ int err;
+
+ err = class_register(&scrub_class);
+ if (err) {
+ pr_err("couldn't register memory scrub control sysfs class\n");
+ return err;
+ }
+
+ return 0;
+}
+subsys_initcall(memory_scrub_control_init);
new file mode 100755
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Memory scrub controller driver support to configure
+ * the controls of the memory scrub and enable.
+ *
+ * Copyright (c) 2023 HiSilicon Limited.
+ */
+
+#ifndef __MEMORY_SCRUB_H
+#define __MEMORY_SCRUB_H
+
+#include <linux/types.h>
+
+enum scrub_types {
+ scrub_common,
+ scrub_max,
+};
+
+enum scrub_attributes {
+ scrub_addr_base,
+ scrub_addr_size,
+ scrub_enable,
+ scrub_enable_background_scrub,
+ scrub_rate,
+ scrub_rate_available,
+ max_attrs,
+};
+
+/**
+ * struct scrub_ops - scrub device operations
+ * @is_visible: Callback to return attribute visibility. Mandatory.
+ * Parameters are:
+ * @dev: pointer to hardware scrub device
+ * @attr: scrub attribute
+ * @region_id: memory region id
+ * The function returns the file permissions.
+ * If the return value is 0, no attribute will be created.
+ * @read: Read callback for data attributes. Mandatory if readable
+ * data attributes are present.
+ * Parameters are:
+ * @dev: pointer to hardware scrub device
+ * @attr: scrub attribute
+ * @region_id:
+ * memory region id
+ * @val: pointer to returned value
+ * The function returns 0 on success or a negative error number.
+ * @read_string: Read callback for string attributes. Mandatory if string
+ * attributes are present.
+ * Parameters are:
+ * @dev: pointer to hardware scrub device
+ * @attr: scrub attribute
+ * @region_id:
+ * memory region id
+ * @buf: pointer to buffer to copy string
+ * The function returns 0 on success or a negative error number.
+ * @write: Write callback for data attributes. Mandatory if writeable
+ * data attributes are present.
+ * Parameters are:
+ * @dev: pointer to hardware scrub device
+ * @attr: scrub attribute
+ * @region_id:
+ * memory region id
+ * @val: value to write
+ * The function returns 0 on success or a negative error number.
+ */
+struct scrub_ops {
+ umode_t (*is_visible)(struct device *dev, u32 attr, int region_id);
+ int (*read)(struct device *dev, u32 attr, int region_id, u64 *val);
+ int (*read_string)(struct device *dev, u32 attr, int region_id, char *buf);
+ int (*write)(struct device *dev, u32 attr, int region_id, u64 val);
+};
+
+struct device *
+devm_scrub_device_register(struct device *dev, const char *name,
+ void *drvdata, const struct scrub_ops *ops,
+ int region_id,
+ struct attribute_group *attr_group);
+#endif /* __MEMORY_SCRUB_H */