From patchwork Tue Nov 21 10:18:39 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 13462763 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 7F6B5C54FB9 for ; Tue, 21 Nov 2023 10:19:18 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id 66F786B0400; Tue, 21 Nov 2023 05:19:09 -0500 (EST) Received: by kanga.kvack.org (Postfix, from userid 40) id 61F9D6B040C; Tue, 21 Nov 2023 05:19:09 -0500 (EST) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 4E5F76B040D; Tue, 21 Nov 2023 05:19:09 -0500 (EST) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0012.hostedemail.com [216.40.44.12]) by kanga.kvack.org (Postfix) with ESMTP id 3929C6B0400 for ; Tue, 21 Nov 2023 05:19:09 -0500 (EST) Received: from smtpin07.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay09.hostedemail.com (Postfix) with ESMTP id 140C280AD3 for ; Tue, 21 Nov 2023 10:19:09 +0000 (UTC) X-FDA: 81481563618.07.CBC16EB Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by imf03.hostedemail.com (Postfix) with ESMTP id E6FAC20014 for ; Tue, 21 Nov 2023 10:19:06 +0000 (UTC) Authentication-Results: imf03.hostedemail.com; dkim=none; dmarc=pass (policy=quarantine) header.from=huawei.com; spf=pass (imf03.hostedemail.com: domain of shiju.jose@huawei.com designates 185.176.79.56 as permitted sender) smtp.mailfrom=shiju.jose@huawei.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1700561947; 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=Ua3tgGsG7V91kZN+Z+W988Po908FEGFnxsLwBoAWiWI=; b=EZnW55KIuP2YHJTECKW4+0tk5Rx9rr2wpu3czkgAnWZNx+qBLJcJge0WuOueDal0Xdt9x+ 7pm0JdJrEdN+q99VP9Q2XawRU+XYQSGx+OipaG0sS2csfvBJtab56t0FRe8hjTzaQAgjSh 6KMw629UlIwS6xGppOcxoRTtgqrtSIA= ARC-Authentication-Results: i=1; imf03.hostedemail.com; dkim=none; dmarc=pass (policy=quarantine) header.from=huawei.com; spf=pass (imf03.hostedemail.com: domain of shiju.jose@huawei.com designates 185.176.79.56 as permitted sender) smtp.mailfrom=shiju.jose@huawei.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1700561947; a=rsa-sha256; cv=none; b=nXbAqgPKU3iMkzmjLsw71hsli7eApC0OlOnMVBtfoKw4vgClm2nvO2ATnbdc0F2hxBVTrK D4bf0laaoPr1bHYEkvjKURh0AHSQ3pvbWdpgWzfogvVjiW6FbpHveqxbcMGMZWqnhvLai5 kCJv8I8VYrIKu6E2xrm3Rketu9SYPuM= Received: from lhrpeml500006.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4SZL0k2NGNz6K8tG; Tue, 21 Nov 2023 18:17:42 +0800 (CST) Received: from SecurePC30232.china.huawei.com (10.122.247.234) by lhrpeml500006.china.huawei.com (7.191.161.198) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.35; Tue, 21 Nov 2023 10:19:04 +0000 From: To: , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 06/10] memory: scrub: Add scrub driver supports configuring memory scrubbers in the system Date: Tue, 21 Nov 2023 18:18:39 +0800 Message-ID: <20231121101844.1161-7-shiju.jose@huawei.com> X-Mailer: git-send-email 2.35.1.windows.2 In-Reply-To: <20231121101844.1161-1-shiju.jose@huawei.com> References: <20231121101844.1161-1-shiju.jose@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.122.247.234] X-ClientProxiedBy: lhrpeml100005.china.huawei.com (7.191.160.25) To lhrpeml500006.china.huawei.com (7.191.161.198) X-CFilter-Loop: Reflected X-Rspamd-Queue-Id: E6FAC20014 X-Rspam-User: X-Rspamd-Server: rspam05 X-Stat-Signature: axd9cgmfocbq3pye9nkfs175p5kk9t5b X-HE-Tag: 1700561946-456913 X-HE-Meta: U2FsdGVkX19N3UOfGWAKMbkMVKZ3B2hiX542lop99T/QBs842z2FaxKhNfnefauD3FxNWqofN8PNyK12+Jjb6/rTRn/oL4BjzJmZsqGOoUBLxG/24lIJClFNhp5kp+MjFVx0tURwOK0BlPxlUHdvJ+g48u8RLhowY9rp27mSvjx4DD5102lCZlLi7Pr3ugrOUNcBSOrEE7X5ka7NdmJ1GT47goaGSmG5GxzdJziPiWY9XaZ1jbjq3AP2pV6VUX6HshCFzeTn1tz5wR90Av2WV/VmlspdGc//aimKzjm/juuk/OYCGtNtEOudJG5Dfa2Sk81Wg7QG20iwbI3Utm3V/acUWpwx6RtC4mfSyHlFj0vZdczTHn4An/YqoLWMjFHYHGkMyKqXNUY0YSUdJAF/Xr4wfUKFwVEy4S+QB5ZP6Hj9wVtZI+WD5xdOoH3JhJa6+V/IBBJnAocEDVcXqaCvvOy9FrYZU1rTbTdQPfNILNWpyoHklKMzipmqYaPgFJFXtEhYtbnuMMDkWQRr5HCFTDyM3qhEH1g+ht0fGfDSRCKfTzSZi6EU369VKXvobV5xUJnFE7NE986VEDAY/Yjhex6scu1MJgo5NlAxwIJ0ilT718z0MA2KbDF33U/OCnffQONPBVU3f1WykvAC4VHSjj3+sLAOznfcPPM2xZnVX235RHmDDQ9ZYj2VMje594h2J6Bz9UInWGlGka1705sRSsHdkSqDwk4xwttvuNAon50r9wZWrnDxKS2pghX6u4gIyIe5rTSS2CF0zrZmdEzn05621rXBpQFQjg8Sd6Hqaf9VmI+98VYLgwMWgtY6Ijx3cNMv6dZfglLkKtwkxRR02TsEO4QKlTuDrI/ZwWmN9xROV6yToB7mix9PGfq/tJ6UE8FYCqSZdFaOKRRMQJ7+bdivmbZ+cGddSuBNge//f6h6j3AWAtD5Q9EPzs30ITOLdulRyGdp835Mdk7Q4xJ +Ca7af2Q Gxt1JvUjUqWbHOUtNU53IwPVUhiP1LSklcMPWXHFdzqclXa8sF+TseuVll6sII6i6CwGwZzrAfvLpdNJT6u0BIUEoj5j6J2yfv2YZAYVihkbmz5Z7YT2dbeYlTnLyiI7k2yB1cBOFZd4sNkQ= 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 Add scrub driver supports configuring the memory scrubbers in the system. The scrub driver provides the interface for registering the scrub devices and to configure the parameters of memory scrubbers in the system. Driver exposes the scrub parameter attributes to the user via sysfs in /sys/class/scrub/scrubX/regionY/ This driver has been implemented referring to the hwmon subsystem. Signed-off-by: Shiju Jose --- drivers/memory/Kconfig | 1 + drivers/memory/Makefile | 1 + drivers/memory/scrub/Kconfig | 11 + drivers/memory/scrub/Makefile | 6 + drivers/memory/scrub/memory-scrub.c | 473 ++++++++++++++++++++++++++++ include/memory/memory-scrub.h | 80 +++++ 6 files changed, 572 insertions(+) create mode 100644 drivers/memory/scrub/Kconfig create mode 100644 drivers/memory/scrub/Makefile create mode 100755 drivers/memory/scrub/memory-scrub.c create mode 100755 include/memory/memory-scrub.h diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 8efdd1f97139..d2e015c09d83 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -227,5 +227,6 @@ config STM32_FMC2_EBI source "drivers/memory/samsung/Kconfig" source "drivers/memory/tegra/Kconfig" +source "drivers/memory/scrub/Kconfig" endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index d2e6ca9abbe0..4b37312cb342 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -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 diff --git a/drivers/memory/scrub/Kconfig b/drivers/memory/scrub/Kconfig new file mode 100644 index 000000000000..fa7d68f53a69 --- /dev/null +++ b/drivers/memory/scrub/Kconfig @@ -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. diff --git a/drivers/memory/scrub/Makefile b/drivers/memory/scrub/Makefile new file mode 100644 index 000000000000..1b677132ca13 --- /dev/null +++ b/drivers/memory/scrub/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for memory scrub drivers +# + +obj-$(CONFIG_SCRUB) += memory-scrub.o diff --git a/drivers/memory/scrub/memory-scrub.c b/drivers/memory/scrub/memory-scrub.c new file mode 100755 index 000000000000..e14e7207b1ad --- /dev/null +++ b/drivers/memory/scrub/memory-scrub.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Memory scrub controller driver support to configure + * the parameters of the memory scrubbers and enable. + * + * Copyright (c) 2023 HiSilicon Limited. + */ + +#define pr_fmt(fmt) "MEM SCRUB: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* memory scrubber config definitions */ +#define SCRUB_ID_PREFIX "scrub" +#define SCRUB_ID_FORMAT SCRUB_ID_PREFIX "%d" +#define SCRUB_DEV_MAX_NAME_LENGTH 128 + +static DEFINE_IDA(scrub_ida); + +struct scrub_device { + char name[SCRUB_DEV_MAX_NAME_LENGTH]; + int id; + struct device dev; + const struct scrub_source_info *source_info; + struct list_head tzdata; + char (*region_name)[]; + struct attribute_group group; + int ngroups; + struct attribute_group *region_groups; + const struct attribute_group **groups; +}; + +#define to_scrub_device(d) container_of(d, struct scrub_device, dev) +#define SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH 64 + +struct scrub_device_attribute { + struct device_attribute dev_attr; + const struct scrub_ops *ops; + u32 attr; + int region_id; + char name[SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH]; +}; + +#define to_scrub_attr(d) \ + container_of(d, struct scrub_device_attribute, dev_attr) +#define to_dev_attr(a) container_of(a, struct device_attribute, attr) + +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) +{ + struct device *dev = kobj_to_dev(kobj); + struct scrub_device *scrub_dev = to_scrub_device(dev); + + if (attr == &dev_attr_name.attr && scrub_dev->name == NULL) + 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_free_attrs(struct attribute **attrs) +{ + int i; + + for (i = 0; attrs[i]; i++) { + struct device_attribute *dattr = to_dev_attr(attrs[i]); + struct scrub_device_attribute *hattr = to_scrub_attr(dattr); + + kfree(hattr); + } + kfree(attrs); +} + +static void scrub_dev_release(struct device *dev) +{ + int count; + struct attribute_group *group; + struct scrub_device *scrub_dev = to_scrub_device(dev); + + for (count = 0; count < scrub_dev->ngroups; count++) { + group = (struct attribute_group *)scrub_dev->groups[count]; + if (group) + scrub_free_attrs(group->attrs); + } + kfree(scrub_dev->region_name); + kfree(scrub_dev->region_groups); + kfree(scrub_dev->groups); + 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, +}; + +/* sysfs attribute management */ + +static ssize_t scrub_attr_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int ret; + u64 val; + struct scrub_device_attribute *hattr = to_scrub_attr(devattr); + + ret = hattr->ops->read(dev, hattr->attr, hattr->region_id, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%lld\n", val); +} + +static ssize_t scrub_attr_show_hex(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int ret; + u64 val; + struct scrub_device_attribute *hattr = to_scrub_attr(devattr); + + ret = hattr->ops->read(dev, hattr->attr, hattr->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, + struct device_attribute *devattr, + char *buf) +{ + int ret; + struct scrub_device_attribute *hattr = to_scrub_attr(devattr); + + ret = hattr->ops->read_string(dev, hattr->attr, hattr->region_id, buf); + if (ret < 0) + return ret; + + return strlen(buf); +} + +static ssize_t scrub_attr_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int ret; + long val; + struct scrub_device_attribute *hattr = to_scrub_attr(devattr); + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + ret = hattr->ops->write(dev, hattr->attr, hattr->region_id, val); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t scrub_attr_store_hex(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int ret; + u64 val; + struct scrub_device_attribute *hattr = to_scrub_attr(devattr); + + ret = kstrtou64(buf, 16, &val); + if (ret < 0) + return ret; + + ret = hattr->ops->write(dev, hattr->attr, hattr->region_id, val); + if (ret < 0) + return ret; + + return count; +} + +static bool is_hex_attr(u32 attr) +{ + return (attr == scrub_addr_base) || + (attr == scrub_addr_size); +} + +static bool is_string_attr(u32 attr) +{ + return attr == scrub_speed_available; +} + +static struct attribute *scrub_genattr(const void *drvdata, + u32 attr, + const char *attrb_name, + const struct scrub_ops *ops, + int region_id) +{ + umode_t mode; + struct attribute *a; + struct device_attribute *dattr; + bool is_hex = is_hex_attr(attr); + struct scrub_device_attribute *hattr; + bool is_string = is_string_attr(attr); + + /* The attribute is invisible if there is no template string */ + if (!attrb_name) + return ERR_PTR(-ENOENT); + + mode = ops->is_visible(drvdata, attr, region_id); + if (!mode) + return ERR_PTR(-ENOENT); + + if ((mode & 0444) && ((is_string && !ops->read_string) || + (!is_string && !ops->read))) + return ERR_PTR(-EINVAL); + if ((mode & 0222) && (!ops->write)) + return ERR_PTR(-EINVAL); + + hattr = kzalloc(sizeof(*hattr), GFP_KERNEL); + if (!hattr) + return ERR_PTR(-ENOMEM); + + hattr->attr = attr; + hattr->ops = ops; + hattr->region_id = region_id; + + dattr = &hattr->dev_attr; + if (is_string) { + dattr->show = scrub_attr_show_string; + } else { + dattr->show = is_hex ? scrub_attr_show_hex : scrub_attr_show; + dattr->store = is_hex ? scrub_attr_store_hex : scrub_attr_store; + } + + a = &dattr->attr; + sysfs_attr_init(a); + a->name = attrb_name; + a->mode = mode; + + return a; +} + +static const char * const scrub_common_attrs[] = { + /* scrub attributes - common */ + [scrub_addr_base] = "addr_base", + [scrub_addr_size] = "addr_size", + [scrub_enable] = "enable", + [scrub_speed] = "speed", + [scrub_speed_available] = "speed_available", +}; + +static struct attribute ** +scrub_create_attrs(const void *drvdata, const struct scrub_ops *ops, int region_id) +{ + u32 attr; + int aindex = 0; + struct attribute *a; + struct attribute **attrs; + + attrs = kcalloc(max_attrs, sizeof(*attrs), GFP_KERNEL); + if (!attrs) + return ERR_PTR(-ENOMEM); + + for (attr = 0; attr < max_attrs; attr++) { + a = scrub_genattr(drvdata, attr, scrub_common_attrs[attr], + ops, region_id); + if (IS_ERR(a)) { + if (PTR_ERR(a) != -ENOENT) { + scrub_free_attrs(attrs); + return ERR_PTR(PTR_ERR(a)); + } + continue; + } + attrs[aindex++] = a; + } + + return attrs; +} + +static struct device * +scrub_device_register(struct device *dev, const char *name, void *drvdata, + const struct scrub_ops *ops, + int nregions) +{ + struct device *hdev; + struct attribute **attrs; + int err, count, region_id; + struct attribute_group *group; + struct scrub_device *scrub_dev; + char (*region_name)[SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH]; + + 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) { + err = -ENOMEM; + goto free_scrub_dev; + } + int ngroups = 2; /* terminating NULL plus &scrub_dev->groups */ + + ngroups += nregions; + + scrub_dev->groups = kcalloc(ngroups, sizeof(struct attribute_group *), GFP_KERNEL); + if (!scrub_dev->groups) { + err = -ENOMEM; + goto free_ida; + } + + if (nregions) { + scrub_dev->region_groups = kcalloc(nregions, sizeof(struct attribute_group), + GFP_KERNEL); + if (!scrub_dev->groups) { + err = -ENOMEM; + goto free_groups; + } + scrub_dev->region_name = kcalloc(nregions, SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH, + GFP_KERNEL); + if (!scrub_dev->region_name) { + err = -ENOMEM; + goto free_region_groups; + } + } + + ngroups = 0; + scrub_dev->ngroups = 0; + if (nregions) { + region_name = scrub_dev->region_name; + for (region_id = 0; region_id < nregions; region_id++) { + attrs = scrub_create_attrs(drvdata, ops, region_id); + if (IS_ERR(attrs)) { + err = PTR_ERR(attrs); + goto free_attrs; + } + snprintf((char *)region_name, SCRUB_MAX_SYSFS_ATTR_NAME_LENGTH, + "region%d", region_id); + scrub_dev->region_groups[region_id].name = (char *)region_name; + scrub_dev->region_groups[region_id].attrs = attrs; + region_name++; + scrub_dev->groups[ngroups++] = &scrub_dev->region_groups[region_id]; + scrub_dev->ngroups = ngroups; + } + } else { + attrs = scrub_create_attrs(drvdata, ops, -1); + if (IS_ERR(attrs)) { + err = PTR_ERR(attrs); + goto free_region_name; + } + scrub_dev->group.attrs = attrs; + scrub_dev->groups[ngroups++] = &scrub_dev->group; + scrub_dev->ngroups = ngroups; + } + + 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; + +free_attrs: + for (count = 0; count < scrub_dev->ngroups; count++) { + group = (struct attribute_group *)scrub_dev->groups[count]; + if (group) + scrub_free_attrs(group->attrs); + } + +free_region_name: + kfree(scrub_dev->region_name); + +free_region_groups: + kfree(scrub_dev->region_groups); + +free_groups: + kfree(scrub_dev->groups); + +free_ida: + ida_free(&scrub_ida, scrub_dev->id); + +free_scrub_dev: + kfree(scrub_dev); + return ERR_PTR(err); +} + +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 (mandatory) + * @name: hw scrubber name attribute (mandatory) + * @drvdata: driver data to attach to created device (mandatory) + * @ops: pointer to scrub_ops structure (mandatory) + * @nregions: number of scrub regions to create (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 nregions) +{ + struct device *hdev; + int ret; + + if (!dev || !name || !ops) + return ERR_PTR(-EINVAL); + + hdev = scrub_device_register(dev, name, drvdata, ops, nregions); + 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); diff --git a/include/memory/memory-scrub.h b/include/memory/memory-scrub.h new file mode 100755 index 000000000000..d7cbde4718d0 --- /dev/null +++ b/include/memory/memory-scrub.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Memory scrub controller driver support to configure + * the parameters of the memory scrubbers and enable. + * + * Copyright (c) 2023 HiSilicon Limited. + */ + +#ifndef __MEMORY_SCRUB_H +#define __MEMORY_SCRUB_H + +#include + +enum scrub_types { + scrub_common, + scrub_max, +}; + +enum scrub_attributes { + /* scrub attributes - common */ + scrub_addr_base, + scrub_addr_size, + scrub_enable, + scrub_speed, + scrub_speed_available, + max_attrs, +}; + +/** + * struct scrub_ops - scrub device operations + * @is_visible: Callback to return attribute visibility. Mandatory. + * Parameters are: + * @drvdata: + * pointer to driver-private data structure passed + * as argument to scrub_device_register(). + * @attr: scrubber 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: scrubber 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: scrubber 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: scrubber 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)(const void *drvdata, 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 nregions); +#endif /* __MEMORY_SCRUB_H */