From patchwork Fri Jul 26 16:05:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 13742947 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 429C5C3DA4A for ; Fri, 26 Jul 2024 16:07:23 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id AE7B96B00AD; Fri, 26 Jul 2024 12:07:14 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id A48216B00AE; Fri, 26 Jul 2024 12:07:14 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 872AE6B00AF; Fri, 26 Jul 2024 12:07:14 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0015.hostedemail.com [216.40.44.15]) by kanga.kvack.org (Postfix) with ESMTP id 5FE4C6B00AD for ; Fri, 26 Jul 2024 12:07:14 -0400 (EDT) Received: from smtpin01.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay09.hostedemail.com (Postfix) with ESMTP id 21F5881180 for ; Fri, 26 Jul 2024 16:07:14 +0000 (UTC) X-FDA: 82382383188.01.E4B50B5 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by imf18.hostedemail.com (Postfix) with ESMTP id 278B41C003F for ; Fri, 26 Jul 2024 16:07:11 +0000 (UTC) Authentication-Results: imf18.hostedemail.com; dkim=none; spf=pass (imf18.hostedemail.com: domain of shiju.jose@huawei.com designates 185.176.79.56 as permitted sender) smtp.mailfrom=shiju.jose@huawei.com; dmarc=pass (policy=quarantine) header.from=huawei.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1722010007; a=rsa-sha256; cv=none; b=NfyDCV1BjpdpvqCT/Y32WqeEcTHY0qGifM6CczL9ykTZBHy/nazsC4kKaRFA9gpk3myJqr RgeX15qXzuaWOP7Sw/zNQ6aKcuqQ8sKBFBUUs7Azzu3xs8g0legoCsrbnQ8k5RZyBIztzL q315gQ+JRAvpYrHh0ksAAx7jstIK2ug= ARC-Authentication-Results: i=1; imf18.hostedemail.com; dkim=none; spf=pass (imf18.hostedemail.com: domain of shiju.jose@huawei.com designates 185.176.79.56 as permitted sender) smtp.mailfrom=shiju.jose@huawei.com; dmarc=pass (policy=quarantine) header.from=huawei.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1722010007; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gvdhSHB7zEszOva1AbGZFKonKMMJDTy24dt9MtuAmU4=; b=vW2qJc4a4BSdCL2CO+aDNGmTQvT61sIxI8jrUAfPmApK9LwKb+lcE59i1cCG5RpjV1Jtvq V0szSOAXAq/LsitRHbO4GROt/IMEbSKT4aUrK/qyoLrU+6Cmo7s4GeWLEeDB5yXiXF5WC2 bDSYpCGjL8yJPlqsFo8Y5ECEViVvL7k= Received: from mail.maildlp.com (unknown [172.18.186.231]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4WVsyf3ft9z6K9KG; Sat, 27 Jul 2024 00:04:42 +0800 (CST) Received: from lhrpeml500006.china.huawei.com (unknown [7.191.161.198]) by mail.maildlp.com (Postfix) with ESMTPS id 32193140A30; Sat, 27 Jul 2024 00:07:10 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.126.173.33) by lhrpeml500006.china.huawei.com (7.191.161.198) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Fri, 26 Jul 2024 17:07:08 +0100 From: To: , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v10 11/11] ras: scrub: ACPI RAS2: Add memory ACPI RAS2 driver Date: Fri, 26 Jul 2024 17:05:55 +0100 Message-ID: <20240726160556.2079-12-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20240726160556.2079-1-shiju.jose@huawei.com> References: <20240726160556.2079-1-shiju.jose@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.126.173.33] X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To lhrpeml500006.china.huawei.com (7.191.161.198) X-Stat-Signature: 7rctx75355edgnjnk7ykqofjazyfcf8e X-Rspamd-Queue-Id: 278B41C003F X-Rspam-User: X-Rspamd-Server: rspam10 X-HE-Tag: 1722010031-185533 X-HE-Meta: U2FsdGVkX1/VZ6ZXwfOIZ13aOeVuGx5IMZugocx7bUuM17llMWidMHGY0wCx+F0ZK3Z39BZrKbGsbnLD6Yr/cQVI3xjDD9L/YoBizxglQCvbbFHzGqOFjaI6otLONuo6XRCXucO2sdB6bZnmZEDrh0fg3HgIRWBEcZaN2PKOR9swSv3UIQAy6Se6qK5u3559B0Az9AS0uG5pAj499hCoIS8ELEJGhz4ldz0IHm8NL9lA7YCh15G7t+XG4IJGsKTmfML2z5mf9HHwNhB7Qx8OLAZ0oOf4n9YpykQKAQnvL1iS6hqQReuPEpWcqSy30+23UrjFowqcztP37a3lbRe4Al9LhSczWbtSyWU9W8kPQQvJyrhatDBX6+oxJT4xRurea34qMb/A4+C8BpkoZkJoN9BxJSG6i007H2kKmRnJkeVuV+vJ7+zKYrvjhFjwQauZkC3c60nH21CehnlrwfVQPT6ENkN1Z2FnwbhAkt4EpmK7XvRluD1VVF0PH7OCLIJ4gGKEFu0iuw89Aqpeu3WJROT9dhpIzo/dODgYqAs4a56+UiEbXXROJeAbIMXv/E7I1CLIXNI1mPGADdsO5iKA1i0gtJT4usJ2u6pqfiYlhMoWUoraOG1+kt35EsHBY9zkzown1sxdiuSPE25byTSsC6Xjjh8ISh2W1ns9sxcatAG0jp5mQq/PuTegKSyWW0DGIJh6+lEZQGp7hpb449Do60pdWXWGddBMeo3gYcrZERlQNunialhYLRQkCljsfKncEzB+JpuFre54JsDe9zZ6wI8iVf9qnfzRU12z3y751r44npQVlp7IwvFhZc0X22wKEdQjK0DzrCBYaZVmpN/yH9Loropv6ycllBxBufheCKdAMtmvdLgYYY6tWvXhjgHpsaH5hL+Kg9c9QjB4VSimlEvb7wckr/6wQmAWK7L4HlXSXOT4NqjLvffZvX0kAbpviqtRfDdjZN/EJHcg0B2 cddxHtpH NMZ7/SVhFgBubYw718HbvlOjaxhM95AAe+Yea0N+ZRPJnEWUDZG+jPJ0LhLDsX5IrtBEEhNCZRYPhbWz2n5kcyKb67xJ/CMd9gLIOwxiMJIketfIAWKnhiMv9n4Z4+Jyo1d86+qq0ek84/JMJ9FUCdlLTx8zG5WF3lLwH946mMHse+Zb/TTwi3H+JwtHNmdPeuQL32neJlENPz73zMqaO2Pu/hA== X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: From: Shiju Jose 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 to the userspace via the edac scrub. Get the scrub attr descriptors from the EDAC scrub and register with EDAC RAS feature driver to expose sysfs scrub control attributes to the userspace. For example scrub control for the RAS2 memory device is exposed in /sys/bus/edac/devices/acpi_ras2_mem0/scrub/ Co-developed-by: Jonathan Cameron Signed-off-by: Jonathan Cameron Signed-off-by: Shiju Jose --- Documentation/scrub/edac-scrub.rst | 41 +++ drivers/ras/Kconfig | 10 + drivers/ras/Makefile | 1 + drivers/ras/acpi_ras2.c | 415 +++++++++++++++++++++++++++++ 4 files changed, 467 insertions(+) create mode 100644 drivers/ras/acpi_ras2.c diff --git a/Documentation/scrub/edac-scrub.rst b/Documentation/scrub/edac-scrub.rst index 7815d674f496..348f98091ac1 100644 --- a/Documentation/scrub/edac-scrub.rst +++ b/Documentation/scrub/edac-scrub.rst @@ -72,3 +72,44 @@ root@localhost:~# cat /sys/bus/edac/devices/cxl_region0/scrub/enable_background root@localhost:~# echo 0 > /sys/bus/edac/devices/cxl_region0/scrub/enable_background root@localhost:~# cat /sys/bus/edac/devices/cxl_region0/scrub/enable_background 0 + +2. RAS2 +2.1 On demand scrubbing for a specific memory region. +root@localhost:~# echo 0x120000 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/addr_range_base +root@localhost:~# echo 0x150000 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/addr_range_size +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/min_cycle_duration +1 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/max_cycle_duration +24 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +10 +root@localhost:~# echo 15 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +root@localhost:~# echo 1 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/enable_on_demand +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/enable_on_demand +1 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +15 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/addr_range_base +0x120000 +root@localhost:~# cat //sys/bus/edac/devices/acpi_ras2_mem0/scrub/addr_range_size +0x150000 +root@localhost:~# echo 0 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/enable_on_demand +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/enable_on_demand +0 + +2.2 Background scrubbing the entire memory +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/min_cycle_duration +1 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/max_cycle_duration +24 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +10 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/enable_background +0 +root@localhost:~# echo 3 > /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +root@localhost:~# echo 1 > /sys/bus/edac/devices/acpi_ras2_mem0/enable_background +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/enable_background +1 +root@localhost:~# cat /sys/bus/edac/devices/acpi_ras2_mem0/scrub/current_cycle_duration +3 +root@localhost:~# echo 0 > /sys/bus/edac/devices/acpi_ras2_mem0/enable_background diff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig index fc4f4bb94a4c..a2635017d80d 100644 --- a/drivers/ras/Kconfig +++ b/drivers/ras/Kconfig @@ -46,4 +46,14 @@ config RAS_FMPM Memory will be retired during boot time and run time depending on platform-specific policies. +config MEM_ACPI_RAS2 + tristate "Memory ACPI RAS2 driver" + depends on ACPI_RAS2 + depends on EDAC + 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 to the user via the edac scrub. + endif diff --git a/drivers/ras/Makefile b/drivers/ras/Makefile index 11f95d59d397..a0e6e903d6b0 100644 --- a/drivers/ras/Makefile +++ b/drivers/ras/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_RAS) += ras.o obj-$(CONFIG_DEBUG_FS) += debugfs.o obj-$(CONFIG_RAS_CEC) += cec.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..b2381d5b37d0 --- /dev/null +++ b/drivers/ras/acpi_ras2.c @@ -0,0 +1,415 @@ +// 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 +#include +#include + +#define RAS2_DEV_NUM_RAS_FEATURES 1 + +#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_SCHRS_IN_MASK GENMASK(15, 8) +#define RAS2_PATROL_SCRUB_EN_BACKGROUND BIT(0) +#define RAS2_PATROL_SCRUB_SCHRS_OUT_MASK GENMASK(7, 0) +#define RAS2_PATROL_SCRUB_MIN_SCHRS_OUT_MASK GENMASK(15, 8) +#define RAS2_PATROL_SCRUB_MAX_SCHRS_OUT_MASK GENMASK(23, 16) +#define RAS2_PATROL_SCRUB_FLAG_SCRUBBER_RUNNING BIT(0) + +#define RAS2_SCRUB_NAME_LEN 128 + +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->min_scrub_cycle = FIELD_GET(RAS2_PATROL_SCRUB_MIN_SCHRS_OUT_MASK, + ps_sm->params.scrub_params_out); + ras2_ctx->max_scrub_cycle = FIELD_GET(RAS2_PATROL_SCRUB_MAX_SCHRS_OUT_MASK, + ps_sm->params.scrub_params_out); + if (!ras2_ctx->bg) { + ras2_ctx->base = ps_sm->params.actual_address_range[0]; + ras2_ctx->size = ps_sm->params.actual_address_range[1]; + } + ras2_ctx->scrub_cycle_secs = FIELD_GET(RAS2_PATROL_SCRUB_SCHRS_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; + + 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_read_min_scrub_cycle(struct device *dev, void *drv_data, + u32 *min) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + *min = ras2_ctx->min_scrub_cycle; + + return 0; +} + +static int ras2_hw_scrub_read_max_scrub_cycle(struct device *dev, void *drv_data, + u32 *max) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + *max = ras2_ctx->max_scrub_cycle; + + return 0; +} + +static int ras2_hw_scrub_cycle_read(struct device *dev, void *drv_data, + u32 *scrub_cycle_secs) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + *scrub_cycle_secs = ras2_ctx->scrub_cycle_secs; + + return 0; +} + +static int ras2_hw_scrub_cycle_write(struct device *dev, void *drv_data, + u32 scrub_cycle_secs) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + 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 (scrub_cycle_secs < ras2_ctx->min_scrub_cycle || + scrub_cycle_secs > ras2_ctx->max_scrub_cycle) + return -EINVAL; + + ras2_ctx->scrub_cycle_secs = scrub_cycle_secs; + + return 0; +} + +static int ras2_hw_scrub_read_range(struct device *dev, void *drv_data, u64 *base, u64 *size) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + /* + * When BG scrubbing is enabled the actual address range is not valid. + * Return -EBUSY now unless findout a method to retrieve actual full PA range. + */ + if (ras2_ctx->bg) + return -EBUSY; + + *base = ras2_ctx->base; + *size = ras2_ctx->size; + + return 0; +} + +static int ras2_hw_scrub_write_range(struct device *dev, void *drv_data, u64 base, u64 size) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + 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 (!base || !size) { + dev_warn(dev, "%s: Invalid address range, base=0x%llx size=0x%llx\n", + __func__, base, size); + return -EINVAL; + } + + ras2_ctx->base = base; + ras2_ctx->size = size; + + return 0; +} + +static int ras2_hw_scrub_set_enabled_bg(struct device *dev, void *drv_data, bool enable) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + 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; + ret = ras2_get_patrol_scrub_running(ras2_ctx, &enabled); + if (ret) + return ret; + if (enable) { + if (ras2_ctx->bg || enabled) + return -EBUSY; + 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_SCHRS_IN_MASK; + ps_sm->params.scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_SCHRS_IN_MASK, + ras2_ctx->scrub_cycle_secs); + ps_sm->params.patrol_scrub_command = RAS2_START_PATROL_SCRUBBER; + } else { + if (!ras2_ctx->bg) + return -EPERM; + if (!ras2_ctx->bg && enabled) + return -EBUSY; + 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; + } + if (enable) { + ras2_ctx->bg = true; + /* Update the cache to account for rounding of supplied parameters and similar */ + ret = ras2_update_patrol_scrub_params_cache(ras2_ctx); + } else { + ret = ras2_update_patrol_scrub_params_cache(ras2_ctx); + ras2_ctx->bg = false; + } + + return ret; +} + +static int ras2_hw_scrub_get_enabled_bg(struct device *dev, void *drv_data, bool *enabled) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + *enabled = ras2_ctx->bg; + + return 0; +} + +static int ras2_hw_scrub_set_enabled_od(struct device *dev, void *drv_data, bool enable) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + 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 (ras2_ctx->bg) + return -EBUSY; + ret = ras2_get_patrol_scrub_running(ras2_ctx, &enabled); + if (ret) + return ret; + if (enable) { + if (!ras2_ctx->base || !ras2_ctx->size) { + dev_warn(ras2_ctx->dev, + "%s: Invalid address range, base=0x%llx " + "size=0x%llx\n", __func__, + ras2_ctx->base, ras2_ctx->size); + return -ERANGE; + } + if (enabled) + return -EBUSY; + ps_sm->params.scrub_params_in &= ~RAS2_PATROL_SCRUB_SCHRS_IN_MASK; + ps_sm->params.scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_SCHRS_IN_MASK, + ras2_ctx->scrub_cycle_secs); + ps_sm->params.requested_address_range[0] = ras2_ctx->base; + ps_sm->params.requested_address_range[1] = ras2_ctx->size; + ps_sm->params.scrub_params_in &= ~RAS2_PATROL_SCRUB_EN_BACKGROUND; + ps_sm->params.patrol_scrub_command = RAS2_START_PATROL_SCRUBBER; + } else { + if (!enabled) + return 0; + 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; + } + + return ras2_update_patrol_scrub_params_cache(ras2_ctx); +} + +static int ras2_hw_scrub_get_enabled_od(struct device *dev, void *drv_data, bool *enabled) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + 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, void *drv_data, char *name) +{ + struct ras2_scrub_ctx *ras2_ctx = drv_data; + + return sysfs_emit(name, "acpi_ras2_mem%d_scrub\n", ras2_ctx->id); +} + +static const struct edac_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, + .min_cycle_read = ras2_hw_scrub_read_min_scrub_cycle, + .max_cycle_read = ras2_hw_scrub_read_max_scrub_cycle, + .cycle_duration_read = ras2_hw_scrub_cycle_read, + .cycle_duration_write = ras2_hw_scrub_cycle_write, +}; + +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 edac_ras_feature ras_features[RAS2_DEV_NUM_RAS_FEATURES]; + char scrub_name[RAS2_SCRUB_NAME_LEN]; + struct ras2_scrub_ctx *ras2_ctx; + int num_ras_features = 0; + 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; + + snprintf(scrub_name, sizeof(scrub_name), "acpi_ras2_mem%d", + ras2_ctx->id); + + ras_features[num_ras_features].feat = RAS_FEAT_SCRUB; + ras_features[num_ras_features].scrub_ops = &ras2_scrub_ops; + ras_features[num_ras_features].scrub_ctx = ras2_ctx; + num_ras_features++; + + return edac_ras_dev_register(&pdev->dev, scrub_name, NULL, + num_ras_features, ras_features); +} + +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");