[RFC PATCH v8 10/10] ras: scrub: ACPI RAS2: Add memory ACPI RAS2 driver
From: shiju.jose
Date: Fri Apr 19 2024 - 12:50:04 EST
From: Shiju Jose <shiju.jose@xxxxxxxxxx>
Memory ACPI RAS2 driver binds to the platform device add by the
ACPI RAS2 table parser.
Driver uses a PCC subspace for communicating with the ACPI compliant
platform to provide control of memory scrub parameters via the scrub
subsystem.
Co-developed-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
Signed-off-by: Shiju Jose <shiju.jose@xxxxxxxxxx>
---
Documentation/scrub/scrub-configure.rst | 33 +++
drivers/ras/Kconfig | 10 +
drivers/ras/Makefile | 1 +
drivers/ras/acpi_ras2.c | 358 ++++++++++++++++++++++++
4 files changed, 402 insertions(+)
create mode 100644 drivers/ras/acpi_ras2.c
diff --git a/Documentation/scrub/scrub-configure.rst b/Documentation/scrub/scrub-configure.rst
index 2275366b60d3..7a1bf87bc6d7 100644
--- a/Documentation/scrub/scrub-configure.rst
+++ b/Documentation/scrub/scrub-configure.rst
@@ -50,3 +50,36 @@ The usage takes the form shown in this example::
# echo 0 > /sys/class/ras/ras0/scrub/enable_background
# cat /sys/class/ras/ras0/scrub/enable_background
# 0
+
+2. RAS2
+2.1 On demand scrubbing for a specific memory region.
+ # echo 0x120000 > /sys/class/ras/ras1/scrub/addr_range_base
+ # echo 0x150000 > /sys/class/ras/ras1/scrub/addr_range_size
+ # cat /sys/class/ras/ras1/scrub/rate_available
+ # 0x1-0x18
+ # echo 20 > /sys/class/ras/ras1/scrub/rate
+ # echo 1 > /sys/class/ras/ras1/scrub/enable_on_demand
+ # cat /sys/class/ras/ras1/scrub/enable_on_demand
+ # 1
+ # cat /sys/class/ras/ras1/scrub/rate
+ # 0x14
+ # cat /sys/class/ras/ras1/scrub/addr_range_base
+ # 0x120000
+ # cat /sys/class/ras/ras1/scrub/addr_range_size
+ # 0x150000
+ # echo 0 > /sys/class/ras/ras1/scrub/enable_on_demand
+ # cat /sys/class/ras/ras1/scrub/enable_on_demand
+ # 0
+
+2.2 Background scrubbing the entire memory
+ # cat /sys/class/ras/ras1/scrub/rate_available
+ # 0x1-0x18
+ # echo 3 > /sys/class/ras/ras1/scrub/rate
+ # echo 1 > /sys/class/ras/ras1/scrub/enable_background
+ # cat /sys/class/ras/ras1/scrub/enable_background
+ # 1
+ # cat /sys/class/ras/ras1/scrub/rate
+ # 0x3
+ # echo 0 > /sys/class/ras/ras1/scrub/enable_background
+ # cat /sys/class/ras/ras1/scrub/enable_background
+ # 0
diff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig
index 181701479564..57c346dfc01f 100644
--- a/drivers/ras/Kconfig
+++ b/drivers/ras/Kconfig
@@ -53,4 +53,14 @@ config SCRUB
configuring the parameters of underlying scrubbers in the
system for the DRAM memories.
+config MEM_ACPI_RAS2
+ tristate "Memory ACPI RAS2 driver"
+ depends on ACPI_RAS2
+ depends on SCRUB
+ help
+ The driver binds to the platform device added by the ACPI RAS2
+ table parser. Use a PCC channel subspace for communicating with
+ the ACPI compliant platform to provide control of memory scrub
+ parameters via the scrub subsystem.
+
endif
diff --git a/drivers/ras/Makefile b/drivers/ras/Makefile
index 89bcf0d84355..48339fee1cb3 100644
--- a/drivers/ras/Makefile
+++ b/drivers/ras/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_RAS) += ras.o
obj-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_RAS_CEC) += cec.o
obj-$(CONFIG_SCRUB) += memory_scrub.o
+obj-$(CONFIG_MEM_ACPI_RAS2) += acpi_ras2.o
obj-$(CONFIG_RAS_FMPM) += amd/fmpm.o
obj-y += amd/atl/
diff --git a/drivers/ras/acpi_ras2.c b/drivers/ras/acpi_ras2.c
new file mode 100644
index 000000000000..b3e9b61367bb
--- /dev/null
+++ b/drivers/ras/acpi_ras2.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ACPI RAS2 memory driver
+ *
+ * Copyright (c) 2024 HiSilicon Limited.
+ *
+ */
+
+#define pr_fmt(fmt) "MEMORY ACPI RAS2: " fmt
+
+#include <linux/memory_scrub.h>
+#include <linux/platform_device.h>
+#include <acpi/ras2_acpi.h>
+
+#define RAS2_SUPPORT_HW_PARTOL_SCRUB BIT(0)
+#define RAS2_TYPE_PATROL_SCRUB 0x0000
+
+#define RAS2_GET_PATROL_PARAMETERS 0x01
+#define RAS2_START_PATROL_SCRUBBER 0x02
+#define RAS2_STOP_PATROL_SCRUBBER 0x03
+
+#define RAS2_PATROL_SCRUB_RATE_IN_MASK GENMASK(15, 8)
+#define RAS2_PATROL_SCRUB_EN_BACKGROUND BIT(0)
+#define RAS2_PATROL_SCRUB_RATE_OUT_MASK GENMASK(7, 0)
+#define RAS2_PATROL_SCRUB_MIN_RATE_OUT_MASK GENMASK(15, 8)
+#define RAS2_PATROL_SCRUB_MAX_RATE_OUT_MASK GENMASK(23, 16)
+#define RAS2_PATROL_SCRUB_FLAG_SCRUBBER_RUNNING BIT(0)
+
+struct acpi_ras2_ps_shared_mem {
+ struct acpi_ras2_shared_memory common;
+ struct acpi_ras2_patrol_scrub_parameter params;
+};
+
+static int ras2_is_patrol_scrub_support(struct ras2_scrub_ctx *ras2_ctx)
+{
+ struct acpi_ras2_shared_memory __iomem *common = (void *)
+ ras2_ctx->pcc_subspace->pcc_comm_addr;
+
+ guard(mutex)(&ras2_ctx->lock);
+ common->set_capabilities[0] = 0;
+
+ return common->features[0] & RAS2_SUPPORT_HW_PARTOL_SCRUB;
+}
+
+static int ras2_update_patrol_scrub_params_cache(struct ras2_scrub_ctx *ras2_ctx)
+{
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = (void *)
+ ras2_ctx->pcc_subspace->pcc_comm_addr;
+ int ret;
+
+ ps_sm->common.set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ ps_sm->params.patrol_scrub_command = RAS2_GET_PATROL_PARAMETERS;
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "failed to read parameters\n");
+ return ret;
+ }
+
+ ras2_ctx->rate_min = FIELD_GET(RAS2_PATROL_SCRUB_MIN_RATE_OUT_MASK,
+ ps_sm->params.scrub_params_out);
+ ras2_ctx->rate_max = FIELD_GET(RAS2_PATROL_SCRUB_MAX_RATE_OUT_MASK,
+ ps_sm->params.scrub_params_out);
+ ras2_ctx->base = ps_sm->params.actual_address_range[0];
+ ras2_ctx->size = ps_sm->params.actual_address_range[1];
+ ras2_ctx->rate = FIELD_GET(RAS2_PATROL_SCRUB_RATE_OUT_MASK,
+ ps_sm->params.scrub_params_out);
+ return 0;
+}
+
+/* Context - lock must be held */
+static int ras2_get_patrol_scrub_running(struct ras2_scrub_ctx *ras2_ctx,
+ bool *running)
+{
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = (void *)
+ ras2_ctx->pcc_subspace->pcc_comm_addr;
+ int ret;
+
+ if (ras2_ctx->bg)
+ *running = true;
+
+ ps_sm->common.set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ ps_sm->params.patrol_scrub_command = RAS2_GET_PATROL_PARAMETERS;
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "failed to read parameters\n");
+ return ret;
+ }
+
+ *running = ps_sm->params.flags & RAS2_PATROL_SCRUB_FLAG_SCRUBBER_RUNNING;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_write_rate(struct device *dev, u64 rate)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+ bool running;
+ int ret;
+
+ guard(mutex)(&ras2_ctx->lock);
+ ret = ras2_get_patrol_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ if (running)
+ return -EBUSY;
+
+ if (rate < ras2_ctx->rate_min || rate > ras2_ctx->rate_max)
+ return -EINVAL;
+
+ ras2_ctx->rate = rate;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_rate(struct device *dev, u64 *rate)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ *rate = ras2_ctx->rate;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_rate_avail(struct device *dev, u64 *min, u64 *max)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ *min = ras2_ctx->rate_min;
+ *max = ras2_ctx->rate_max;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_read_range(struct device *dev, u64 *base, u64 *size)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ *base = ras2_ctx->base;
+ *size = ras2_ctx->size;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_write_range(struct device *dev, u64 base, u64 size)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+ bool running;
+ int ret;
+
+ guard(mutex)(&ras2_ctx->lock);
+ ret = ras2_get_patrol_scrub_running(ras2_ctx, &running);
+ if (ret)
+ return ret;
+
+ if (running)
+ return -EBUSY;
+
+ ras2_ctx->base = base;
+ ras2_ctx->size = size;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_bg(struct device *dev, bool enable)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = (void *)
+ ras2_ctx->pcc_subspace->pcc_comm_addr;
+ int ret;
+
+ guard(mutex)(&ras2_ctx->lock);
+ ps_sm->common.set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ if (enable) {
+ ps_sm->params.requested_address_range[0] = 0;
+ ps_sm->params.requested_address_range[1] = 0;
+ ps_sm->params.scrub_params_in &= ~RAS2_PATROL_SCRUB_RATE_IN_MASK;
+ ps_sm->params.scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_RATE_IN_MASK,
+ ras2_ctx->rate);
+ ps_sm->params.patrol_scrub_command = RAS2_START_PATROL_SCRUBBER;
+ } else {
+ ps_sm->params.patrol_scrub_command = RAS2_STOP_PATROL_SCRUBBER;
+ }
+ ps_sm->params.scrub_params_in &= ~RAS2_PATROL_SCRUB_EN_BACKGROUND;
+ ps_sm->params.scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_EN_BACKGROUND,
+ enable);
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "%s: failed to enable(%d) background scrubbing\n",
+ __func__, enable);
+ return ret;
+ }
+ ras2_ctx->bg = true;
+
+ /* Update the cache to account for rounding of supplied parameters and similar */
+ return ras2_update_patrol_scrub_params_cache(ras2_ctx);
+}
+
+static int ras2_hw_scrub_get_enabled_bg(struct device *dev, bool *enabled)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ *enabled = ras2_ctx->bg;
+
+ return 0;
+}
+
+static int ras2_hw_scrub_set_enabled_od(struct device *dev, bool enable)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+ struct acpi_ras2_ps_shared_mem __iomem *ps_sm = (void *)
+ ras2_ctx->pcc_subspace->pcc_comm_addr;
+ bool enabled;
+ int ret;
+
+ guard(mutex)(&ras2_ctx->lock);
+ ps_sm->common.set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ if (enable) {
+ if (!ras2_ctx->size) {
+ dev_warn(ras2_ctx->dev,
+ "%s: Invalid requested address range, requested_address_range[0]=0x%llx "
+ "requested_address_range[1]=0x%llx\n", __func__,
+ ps_sm->params.requested_address_range[0],
+ ps_sm->params.requested_address_range[1]);
+ return -ERANGE;
+ }
+ ret = ras2_get_patrol_scrub_running(ras2_ctx, &enabled);
+ if (ret)
+ return ret;
+
+ if (enabled)
+ return 0;
+
+ ps_sm->params.scrub_params_in &= ~RAS2_PATROL_SCRUB_RATE_IN_MASK;
+ ps_sm->params.scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_RATE_IN_MASK,
+ ras2_ctx->rate);
+ ps_sm->params.requested_address_range[0] = ras2_ctx->base;
+ ps_sm->params.requested_address_range[1] = ras2_ctx->size;
+ ps_sm->params.patrol_scrub_command = RAS2_START_PATROL_SCRUBBER;
+ } else {
+ ps_sm->params.patrol_scrub_command = RAS2_STOP_PATROL_SCRUBBER;
+ }
+
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "failed to enable(%d) the demand scrubbing\n", enable);
+ return ret;
+ }
+ ras2_ctx->bg = false;
+
+ return ras2_update_patrol_scrub_params_cache(ras2_ctx);
+}
+
+static int ras2_hw_scrub_get_enabled_od(struct device *dev, bool *enabled)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ guard(mutex)(&ras2_ctx->lock);
+ if (ras2_ctx->bg) {
+ *enabled = false;
+ return 0;
+ }
+
+ return ras2_get_patrol_scrub_running(ras2_ctx, enabled);
+}
+
+static int ras2_hw_scrub_get_name(struct device *dev, char *name)
+{
+ struct ras2_scrub_ctx *ras2_ctx = dev_get_drvdata(dev);
+
+ return sysfs_emit(name, "ras2_scrub%d\n", ras2_ctx->id);
+}
+
+static const struct scrub_ops ras2_scrub_ops = {
+ .read_range = ras2_hw_scrub_read_range,
+ .write_range = ras2_hw_scrub_write_range,
+ .get_enabled_bg = ras2_hw_scrub_get_enabled_bg,
+ .set_enabled_bg = ras2_hw_scrub_set_enabled_bg,
+ .get_enabled_od = ras2_hw_scrub_get_enabled_od,
+ .set_enabled_od = ras2_hw_scrub_set_enabled_od,
+ .get_name = ras2_hw_scrub_get_name,
+ .rate_avail_range = ras2_hw_scrub_read_rate_avail,
+ .rate_read = ras2_hw_scrub_read_rate,
+ .rate_write = ras2_hw_scrub_write_rate,
+};
+
+static DEFINE_IDA(ras2_ida);
+
+static void ida_release(void *ctx)
+{
+ struct ras2_scrub_ctx *ras2_ctx = ctx;
+
+ ida_free(&ras2_ida, ras2_ctx->id);
+}
+
+static int ras2_probe(struct platform_device *pdev)
+{
+ struct ras2_scrub_ctx *ras2_ctx;
+ struct device *hw_scrub_dev;
+ int ret, id;
+
+ /* RAS2 PCC Channel and Scrub specific context */
+ ras2_ctx = devm_kzalloc(&pdev->dev, sizeof(*ras2_ctx), GFP_KERNEL);
+ if (!ras2_ctx)
+ return -ENOMEM;
+
+ ras2_ctx->dev = &pdev->dev;
+ mutex_init(&ras2_ctx->lock);
+
+ ret = devm_ras2_register_pcc_channel(&pdev->dev, ras2_ctx,
+ *((int *)dev_get_platdata(&pdev->dev)));
+ if (ret < 0) {
+ dev_dbg(ras2_ctx->dev,
+ "failed to register pcc channel ret=%d\n", ret);
+ return ret;
+ }
+ if (!ras2_is_patrol_scrub_support(ras2_ctx))
+ return -EOPNOTSUPP;
+
+ ret = ras2_update_patrol_scrub_params_cache(ras2_ctx);
+ if (ret)
+ return ret;
+
+ id = ida_alloc(&ras2_ida, GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ ras2_ctx->id = id;
+
+ ret = devm_add_action_or_reset(&pdev->dev, ida_release, ras2_ctx);
+ if (ret < 0)
+ return ret;
+
+ hw_scrub_dev = devm_scrub_device_register(&pdev->dev, ras2_ctx, &ras2_scrub_ops);
+ if (IS_ERR(hw_scrub_dev))
+ return PTR_ERR(hw_scrub_dev);
+
+ ras2_ctx->scrub_dev = hw_scrub_dev;
+
+ return 0;
+}
+
+static const struct platform_device_id ras2_id_table[] = {
+ { .name = "acpi_ras2", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, ras2_id_table);
+
+static struct platform_driver ras2_driver = {
+ .probe = ras2_probe,
+ .driver = {
+ .name = "acpi_ras2",
+ },
+ .id_table = ras2_id_table,
+};
+module_driver(ras2_driver, platform_driver_register, platform_driver_unregister);
+
+MODULE_IMPORT_NS(ACPI_RAS2);
+MODULE_DESCRIPTION("ACPI RAS2 memory driver");
+MODULE_LICENSE("GPL");
--
2.34.1