From patchwork Thu Mar 20 18:04:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024298 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7F55C1DE3A8; Thu, 20 Mar 2025 18:05:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493935; cv=none; b=cF7it1hFNfD2Zk4+j7k5O1Uy01qr+oCk3XkMTn6he32IgwhatiYVd3Bi7jd75qYQxEZQPqBtfYRAN9wjr6rb2/vNHSMHwtxmMXsLYUJdMi+TKS5uCtZ4/mSdaFUV6WogWAOPUNq48vlGbiqaa5cliuBl3R35mN6NvoZS5jEcHSk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493935; c=relaxed/simple; bh=GtKULYl0mYctQnoh3gUdY0HmdBwjuAmD9xWw0mVxGPM=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YN5eWHpj03ShKbWeN7bQejlXDKyzxhCF0w6/tqp4GESJl4Xi2oxS2hdn0oIHzGdkXkOS0dtjhUhf2UQVzf7MiViJicFlBFlkoFhA1dDFtAdzOPqtLcWGFvzhG53+m1uMfX7rlg+XBqfCS8Vs8hT5/J7u5HctM3QaXrVsLUjEz0Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.231]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMD2qYjz6K99G; Fri, 21 Mar 2025 02:02:32 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id E283B14050D; Fri, 21 Mar 2025 02:05:30 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:28 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 1/8] cxl: Add helper function to retrieve a feature entry Date: Thu, 20 Mar 2025 18:04:38 +0000 Message-ID: <20250320180450.539-2-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Add helper function to retrieve a feature entry from the supported features list, if supported. Reviewed-by: Jonathan Cameron Reviewed-by: Fan Ni Signed-off-by: Shiju Jose --- drivers/cxl/core/core.h | 2 ++ drivers/cxl/core/features.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 1803aedb25ca..16bc717376fc 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -123,6 +123,8 @@ int cxl_ras_init(void); void cxl_ras_exit(void); #ifdef CONFIG_CXL_FEATURES +struct cxl_feat_entry *cxl_get_feature_entry(struct cxl_dev_state *cxlds, + const uuid_t *feat_uuid); size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, enum cxl_get_feat_selection selection, void *feat_out, size_t feat_out_size, u16 offset, diff --git a/drivers/cxl/core/features.c b/drivers/cxl/core/features.c index 048ba4fc3538..202c8c21930c 100644 --- a/drivers/cxl/core/features.c +++ b/drivers/cxl/core/features.c @@ -203,6 +203,29 @@ int devm_cxl_setup_features(struct cxl_dev_state *cxlds) } EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_features, "CXL"); +struct cxl_feat_entry *cxl_get_feature_entry(struct cxl_dev_state *cxlds, + const uuid_t *feat_uuid) +{ + struct cxl_features_state *cxlfs = to_cxlfs(cxlds); + struct cxl_feat_entry *feat_entry; + int count; + + if (!cxlfs || !cxlfs->entries || !cxlfs->entries->num_features) + return NULL; + + /* + * Retrieve the feature entry from the supported features list, + * if the feature is supported. + */ + feat_entry = cxlfs->entries->ent; + for (count = 0; count < cxlfs->entries->num_features; count++, feat_entry++) { + if (uuid_equal(&feat_entry->uuid, feat_uuid)) + return feat_entry; + } + + return NULL; +} + size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, enum cxl_get_feat_selection selection, void *feat_out, size_t feat_out_size, u16 offset, From patchwork Thu Mar 20 18:04:39 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024299 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 11808227B83; Thu, 20 Mar 2025 18:05:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493938; cv=none; b=Z4cs60hyWNSVBazxxZ4WkJy4frEzMAFAeZVUgCZ5LvXPjY9+7OWZHtS7dw+qWmy6yrnXX8XkIqwFLjYVV4n1BRglvaqxMexqoe2aaOjUrXhYzehlWeMYxc+NV/Ibk6nSLUvPbyzA9wyHbHs6ZsQCectQ1rPAFbLRo02DErE9M3A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493938; c=relaxed/simple; bh=SD+DdQiarUAS24vNkjCi+X85etZk11V+cPYKINi+TOE=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=vFGwKJ/dZTECKgGXe/SFfaa+NQEevFKHsVY14xqMcyFV8Eyio8o7M77L8GFs21EiXQXIJrBUuhNCL4A/SFp2ukY0dQx9xTIhWaonSLQEnrCRawGlWmUJVgbGD3Qy/BQby7ztGg9AeUgIGHHCqwSewtlTApNJco1jDIqMrxi2UtQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.231]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYK32CMbz6L58c; Fri, 21 Mar 2025 02:00:39 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id CBD7314050D; Fri, 21 Mar 2025 02:05:33 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:31 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 2/8] EDAC: Update documentation for the CXL memory patrol scrub control feature Date: Thu, 20 Mar 2025 18:04:39 +0000 Message-ID: <20250320180450.539-3-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Update the Documentation/edac/scrub.rst to include descriptions and policies for CXL memory device-based and CXL region-based patrol scrub control. Note: This may require inputs from CXL memory experts regarding region-based scrubbing policies. Signed-off-by: Shiju Jose --- Documentation/edac/scrub.rst | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Documentation/edac/scrub.rst b/Documentation/edac/scrub.rst index daab929cdba1..d1c02bd90090 100644 --- a/Documentation/edac/scrub.rst +++ b/Documentation/edac/scrub.rst @@ -264,3 +264,51 @@ Sysfs files are documented in `Documentation/ABI/testing/sysfs-edac-scrub` `Documentation/ABI/testing/sysfs-edac-ecs` + +Examples +-------- + +The usage takes the form shown in these examples: + +1. CXL memory device patrol scrubber + +1.1 Device based scrubbing + +CXL memory is exposed to memory management subsystem and ultimately userspace +via CXL devices. + +For cases where hardware interleave controls do not directly map to regions of +Physical Address space, perhaps due to interleave the approach described inĀ  +1.2 Region based scrubbing section, which is specific to CXL regions should be +followed. In those cases settings on the presented interface may interact with +direct control via a device instance specific interface and care must be taken. + +Sysfs files for scrubbing are documented in +`Documentation/ABI/testing/sysfs-edac-scrub` + +1.2. Region based scrubbing + +CXL memory is exposed to memory management subsystem and ultimately userspace +via CXL regions. CXL Regions represent mapped memory capacity in system +physical address space. These can incorporate one or more parts of multiple CXL +memory devices with traffic interleaved across them. The user may want to control +the scrub rate via this more abstract region instead of having to figure out the +constituent devices and program them separately. The scrub rate for each device +covers the whole device. Thus if multiple regions use parts of that device then +requests for scrubbing of other regions may result in a higher scrub rate than +requested for this specific region. + +1. When user sets scrub rate for a memory region, the scrub rate for all the CXL + memory devices interleaved under that region is updated with the same scrub + rate. + +2. When user sets scrub rate for a memory device, only the scrub rate for that + memory devices is updated though device may be part of a memory region and + does not change scrub rate of other memory devices of that memory region. + +3. Scrub rate of a CXL memory device may be set via EDAC device or region scrub + interface simultaneously. Care must be taken to prevent a race condition, or + only region-based setting may be allowed. + +Sysfs files for scrubbing are documented in +`Documentation/ABI/testing/sysfs-edac-scrub` From patchwork Thu Mar 20 18:04:40 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024300 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F3956227EA7; Thu, 20 Mar 2025 18:05:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493941; cv=none; b=VYjT99u2KSqUu7+892xRkq3TPZXKWA3L7Z5exvuHlIUoCI7RKumsNtFRsJ+9O7XVAOwnY8GQ6m69jw7oEgFM/bRg0hhWwZJym30MQTN6B/zbvJ5h3sq2PLHUTmgAcGgKZ/9dDXYJf7mPv9tIrvF+2zkoURLgXs8Fsy2s2spSYiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493941; c=relaxed/simple; bh=pZvskOx5Yva5/Kj7rLDynxzs73a9DM7INm8rxrfzf2I=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=gVxwlcHC3xrnlr0EVuOy7h6LMW8rnGobv1Iqopy3WxRwTNa5skdPg3DoEI29BaSEFMzw7/USZC48xNz9mKW5JNJlxcjiAgBzYXIdOcxISClPC8CIZXeJRz8RRimYlzpDbjJCIcTYcbRohQThcMJCSwflr9uy+Aho7HM+Yc1rPbM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.31]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYK62kznz6L5BZ; Fri, 21 Mar 2025 02:00:42 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id DE60414050A; Fri, 21 Mar 2025 02:05:36 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:34 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 3/8] cxl/edac: Add CXL memory device patrol scrub control feature Date: Thu, 20 Mar 2025 18:04:40 +0000 Message-ID: <20250320180450.539-4-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose CXL spec 3.2 section 8.2.10.9.11.1 describes the device patrol scrub control feature. The device patrol scrub proactively locates and makes corrections to errors in regular cycle. Allow specifying the number of hours within which the patrol scrub must be completed, subject to minimum and maximum limits reported by the device. Also allow disabling scrub allowing trade-off error rates against performance. Add support for patrol scrub control on CXL memory devices. Register with the EDAC device driver, which retrieves the scrub attribute descriptors from EDAC scrub and exposes the sysfs scrub control attributes to userspace. For example, scrub control for the CXL memory device "cxl_mem0" is exposed in /sys/bus/edac/devices/cxl_mem0/scrubX/. Additionally, add support for region-based CXL memory patrol scrub control. CXL memory regions may be interleaved across one or more CXL memory devices. For example, region-based scrub control for "cxl_region1" is exposed in /sys/bus/edac/devices/cxl_region1/scrubX/. Reviewed-by: Dave Jiang Co-developed-by: Jonathan Cameron Signed-off-by: Jonathan Cameron Signed-off-by: Shiju Jose --- drivers/cxl/Kconfig | 25 ++ drivers/cxl/core/Makefile | 1 + drivers/cxl/core/edac.c | 474 ++++++++++++++++++++++++++++++++++++++ drivers/cxl/core/region.c | 5 + drivers/cxl/cxlmem.h | 10 + drivers/cxl/mem.c | 4 + 6 files changed, 519 insertions(+) create mode 100644 drivers/cxl/core/edac.c diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig index 205547e5543a..b5ede1308425 100644 --- a/drivers/cxl/Kconfig +++ b/drivers/cxl/Kconfig @@ -113,6 +113,31 @@ config CXL_FEATURES If unsure say 'n' +config CXL_EDAC_MEM_FEATURES + bool "CXL: EDAC Memory Features" + depends on EXPERT + depends on CXL_MEM + depends on CXL_FEATURES + depends on EDAC >= CXL_BUS + depends on EDAC_SCRUB + help + The CXL EDAC memory feature control is optional and allows host + to control the EDAC memory features configurations of CXL memory + expander devices. + + When enabled 'cxl_mem' and 'cxl_region' EDAC devices are published + with memory scrub control attributes as described by + Documentation/ABI/testing/sysfs-edac-scrub. + + When enabled 'cxl_mem' EDAC devices are published with memory ECS + and repair control attributes as described by + Documentation/ABI/testing/sysfs-edac-ecs and + Documentation/ABI/testing/sysfs-edac-memory-repair respectively. + + Say 'y/m' if you have an expert need to change default settings + of a memory RAS feature established by the platform/device (eg. + scrub rates for the patrol scrub feature). otherwise say 'n'. + config CXL_PORT default CXL_BUS tristate diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile index 139b349b3a52..9b86fb22e5de 100644 --- a/drivers/cxl/core/Makefile +++ b/drivers/cxl/core/Makefile @@ -19,4 +19,5 @@ cxl_core-y += ras.o cxl_core-$(CONFIG_TRACING) += trace.o cxl_core-$(CONFIG_CXL_REGION) += region.o cxl_core-$(CONFIG_CXL_FEATURES) += features.o +cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o cxl_core-$(CONFIG_CXL_MCE) += mce.o diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c new file mode 100644 index 000000000000..5ec3535785e1 --- /dev/null +++ b/drivers/cxl/core/edac.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CXL EDAC memory feature driver. + * + * Copyright (c) 2024-2025 HiSilicon Limited. + * + * - Supports functions to configure EDAC features of the + * CXL memory devices. + * - Registers with the EDAC device subsystem driver to expose + * the features sysfs attributes to the user for configuring + * CXL memory RAS feature. + */ + +#include +#include +#include +#include +#include +#include +#include "core.h" + +#define CXL_NR_EDAC_DEV_FEATURES 1 + +static struct rw_semaphore *cxl_acquire(struct rw_semaphore *rwsem) +{ + if (down_read_interruptible(rwsem)) + return NULL; + + return rwsem; +} + +DEFINE_FREE(cxl_unlock, struct rw_semaphore *, if (_T) up_read(_T)) + +/* + * CXL memory patrol scrub control + */ +struct cxl_patrol_scrub_context { + u8 instance; + u16 get_feat_size; + u16 set_feat_size; + u8 get_version; + u8 set_version; + u16 effects; + struct cxl_memdev *cxlmd; + struct cxl_region *cxlr; +}; + +/** + * struct cxl_memdev_ps_params - CXL memory patrol scrub parameter data structure. + * @enable: [IN & OUT] enable(1)/disable(0) patrol scrub. + * @scrub_cycle_changeable: [OUT] scrub cycle attribute of patrol scrub is changeable. + * @scrub_cycle_hrs: [IN] Requested patrol scrub cycle in hours. + * [OUT] Current patrol scrub cycle in hours. + * @min_scrub_cycle_hrs:[OUT] minimum patrol scrub cycle in hours supported. + */ +struct cxl_memdev_ps_params { + bool enable; + bool scrub_cycle_changeable; + u8 scrub_cycle_hrs; + u8 min_scrub_cycle_hrs; +}; + +enum cxl_scrub_param { + CXL_PS_PARAM_ENABLE, + CXL_PS_PARAM_SCRUB_CYCLE, +}; + +#define CXL_MEMDEV_PS_SCRUB_CYCLE_CHANGE_CAP_MASK BIT(0) +#define CXL_MEMDEV_PS_SCRUB_CYCLE_REALTIME_REPORT_CAP_MASK BIT(1) +#define CXL_MEMDEV_PS_CUR_SCRUB_CYCLE_MASK GENMASK(7, 0) +#define CXL_MEMDEV_PS_MIN_SCRUB_CYCLE_MASK GENMASK(15, 8) +#define CXL_MEMDEV_PS_FLAG_ENABLED_MASK BIT(0) + +/* + * See CXL spec rev 3.2 @8.2.10.9.11.1 Table 8-222 Device Patrol Scrub Control + * Feature Readable Attributes. + */ +struct cxl_memdev_ps_rd_attrs { + u8 scrub_cycle_cap; + __le16 scrub_cycle_hrs; + u8 scrub_flags; +} __packed; + +/* + * See CXL spec rev 3.2 @8.2.10.9.11.1 Table 8-223 Device Patrol Scrub Control + * Feature Writable Attributes. + */ +struct cxl_memdev_ps_wr_attrs { + u8 scrub_cycle_hrs; + u8 scrub_flags; +} __packed; + +static int cxl_mem_ps_get_attrs(struct cxl_mailbox *cxl_mbox, + struct cxl_memdev_ps_params *params) +{ + size_t rd_data_size = sizeof(struct cxl_memdev_ps_rd_attrs); + u16 scrub_cycle_hrs; + size_t data_size; + struct cxl_memdev_ps_rd_attrs *rd_attrs __free(kfree) = + kzalloc(rd_data_size, GFP_KERNEL); + if (!rd_attrs) + return -ENOMEM; + + data_size = cxl_get_feature(cxl_mbox, &CXL_FEAT_PATROL_SCRUB_UUID, + CXL_GET_FEAT_SEL_CURRENT_VALUE, rd_attrs, + rd_data_size, 0, NULL); + if (!data_size) + return -EIO; + + params->scrub_cycle_changeable = + FIELD_GET(CXL_MEMDEV_PS_SCRUB_CYCLE_CHANGE_CAP_MASK, + rd_attrs->scrub_cycle_cap); + params->enable = FIELD_GET(CXL_MEMDEV_PS_FLAG_ENABLED_MASK, + rd_attrs->scrub_flags); + scrub_cycle_hrs = le16_to_cpu(rd_attrs->scrub_cycle_hrs); + params->scrub_cycle_hrs = + FIELD_GET(CXL_MEMDEV_PS_CUR_SCRUB_CYCLE_MASK, scrub_cycle_hrs); + params->min_scrub_cycle_hrs = + FIELD_GET(CXL_MEMDEV_PS_MIN_SCRUB_CYCLE_MASK, scrub_cycle_hrs); + + return 0; +} + +static int cxl_ps_get_attrs(struct cxl_patrol_scrub_context *cxl_ps_ctx, + struct cxl_memdev_ps_params *params) +{ + struct cxl_mailbox *cxl_mbox; + struct cxl_memdev *cxlmd; + u8 min_scrub_cycle = U8_MAX; + int i, ret; + + if (cxl_ps_ctx->cxlr) { + struct cxl_region *cxlr = cxl_ps_ctx->cxlr; + struct cxl_region_params *p = &cxlr->params; + + struct rw_semaphore *region_lock __free(cxl_unlock) = + cxl_acquire(&cxl_region_rwsem); + if (!region_lock) + return -EINTR; + + for (i = 0; i < p->nr_targets; i++) { + struct cxl_endpoint_decoder *cxled = p->targets[i]; + + cxlmd = cxled_to_memdev(cxled); + cxl_mbox = &cxlmd->cxlds->cxl_mbox; + ret = cxl_mem_ps_get_attrs(cxl_mbox, params); + if (ret) + return ret; + + min_scrub_cycle = min(params->min_scrub_cycle_hrs, + min_scrub_cycle); + } + + params->min_scrub_cycle_hrs = min_scrub_cycle; + return 0; + } + cxl_mbox = &cxl_ps_ctx->cxlmd->cxlds->cxl_mbox; + + return cxl_mem_ps_get_attrs(cxl_mbox, params); +} + +static int cxl_mem_ps_set_attrs(struct device *dev, + struct cxl_patrol_scrub_context *cxl_ps_ctx, + struct cxl_mailbox *cxl_mbox, + struct cxl_memdev_ps_params *params, + enum cxl_scrub_param param_type) +{ + struct cxl_memdev_ps_wr_attrs wr_attrs; + struct cxl_memdev_ps_params rd_params; + int ret; + + ret = cxl_mem_ps_get_attrs(cxl_mbox, &rd_params); + if (ret) { + dev_dbg(dev, + "Get cxlmemdev patrol scrub params failed ret=%d\n", + ret); + return ret; + } + + switch (param_type) { + case CXL_PS_PARAM_ENABLE: + wr_attrs.scrub_flags = FIELD_PREP(CXL_MEMDEV_PS_FLAG_ENABLED_MASK, + params->enable); + wr_attrs.scrub_cycle_hrs = + FIELD_PREP(CXL_MEMDEV_PS_CUR_SCRUB_CYCLE_MASK, + rd_params.scrub_cycle_hrs); + break; + case CXL_PS_PARAM_SCRUB_CYCLE: + if (params->scrub_cycle_hrs < rd_params.min_scrub_cycle_hrs) { + dev_dbg(dev, + "Invalid CXL patrol scrub cycle(%d) to set\n", + params->scrub_cycle_hrs); + dev_dbg(dev, + "Minimum supported CXL patrol scrub cycle in hour %d\n", + rd_params.min_scrub_cycle_hrs); + return -EINVAL; + } + wr_attrs.scrub_cycle_hrs = + FIELD_PREP(CXL_MEMDEV_PS_CUR_SCRUB_CYCLE_MASK, + params->scrub_cycle_hrs); + wr_attrs.scrub_flags = FIELD_PREP(CXL_MEMDEV_PS_FLAG_ENABLED_MASK, + rd_params.enable); + break; + } + + ret = cxl_set_feature(cxl_mbox, &CXL_FEAT_PATROL_SCRUB_UUID, + cxl_ps_ctx->set_version, &wr_attrs, + sizeof(wr_attrs), + CXL_SET_FEAT_FLAG_DATA_SAVED_ACROSS_RESET, 0, + NULL); + if (ret) { + dev_dbg(dev, "CXL patrol scrub set feature failed ret=%d\n", + ret); + return ret; + } + + return 0; +} + +static int cxl_ps_set_attrs(struct device *dev, + struct cxl_patrol_scrub_context *cxl_ps_ctx, + struct cxl_memdev_ps_params *params, + enum cxl_scrub_param param_type) +{ + struct cxl_mailbox *cxl_mbox; + struct cxl_memdev *cxlmd; + int ret, i; + + if (cxl_ps_ctx->cxlr) { + struct cxl_region *cxlr = cxl_ps_ctx->cxlr; + struct cxl_region_params *p = &cxlr->params; + + struct rw_semaphore *region_lock __free(cxl_unlock) = + cxl_acquire(&cxl_region_rwsem); + if (!region_lock) + return -EINTR; + + for (i = 0; i < p->nr_targets; i++) { + struct cxl_endpoint_decoder *cxled = p->targets[i]; + + cxlmd = cxled_to_memdev(cxled); + cxl_mbox = &cxlmd->cxlds->cxl_mbox; + ret = cxl_mem_ps_set_attrs(dev, cxl_ps_ctx, cxl_mbox, + params, param_type); + if (ret) + return ret; + } + + return 0; + } + cxl_mbox = &cxl_ps_ctx->cxlmd->cxlds->cxl_mbox; + + return cxl_mem_ps_set_attrs(dev, cxl_ps_ctx, cxl_mbox, params, + param_type); +} + +static int cxl_patrol_scrub_get_enabled_bg(struct device *dev, void *drv_data, + bool *enabled) +{ + struct cxl_patrol_scrub_context *ctx = drv_data; + struct cxl_memdev_ps_params params; + int ret; + + ret = cxl_ps_get_attrs(ctx, ¶ms); + if (ret) + return ret; + + *enabled = params.enable; + + return 0; +} + +static int cxl_patrol_scrub_set_enabled_bg(struct device *dev, void *drv_data, + bool enable) +{ + struct cxl_patrol_scrub_context *ctx = drv_data; + struct cxl_memdev_ps_params params = { + .enable = enable, + }; + + return cxl_ps_set_attrs(dev, ctx, ¶ms, CXL_PS_PARAM_ENABLE); +} + +static int cxl_patrol_scrub_read_min_scrub_cycle(struct device *dev, + void *drv_data, u32 *min) +{ + struct cxl_patrol_scrub_context *ctx = drv_data; + struct cxl_memdev_ps_params params; + int ret; + + ret = cxl_ps_get_attrs(ctx, ¶ms); + if (ret) + return ret; + *min = params.min_scrub_cycle_hrs * 3600; + + return 0; +} + +static int cxl_patrol_scrub_read_max_scrub_cycle(struct device *dev, + void *drv_data, u32 *max) +{ + *max = U8_MAX * 3600; /* Max set by register size */ + + return 0; +} + +static int cxl_patrol_scrub_read_scrub_cycle(struct device *dev, void *drv_data, + u32 *scrub_cycle_secs) +{ + struct cxl_patrol_scrub_context *ctx = drv_data; + struct cxl_memdev_ps_params params; + int ret; + + ret = cxl_ps_get_attrs(ctx, ¶ms); + if (ret) + return ret; + + *scrub_cycle_secs = params.scrub_cycle_hrs * 3600; + + return 0; +} + +static int cxl_patrol_scrub_write_scrub_cycle(struct device *dev, + void *drv_data, + u32 scrub_cycle_secs) +{ + struct cxl_patrol_scrub_context *ctx = drv_data; + struct cxl_memdev_ps_params params = { + .scrub_cycle_hrs = scrub_cycle_secs / 3600, + }; + + return cxl_ps_set_attrs(dev, ctx, ¶ms, CXL_PS_PARAM_SCRUB_CYCLE); +} + +static const struct edac_scrub_ops cxl_ps_scrub_ops = { + .get_enabled_bg = cxl_patrol_scrub_get_enabled_bg, + .set_enabled_bg = cxl_patrol_scrub_set_enabled_bg, + .get_min_cycle = cxl_patrol_scrub_read_min_scrub_cycle, + .get_max_cycle = cxl_patrol_scrub_read_max_scrub_cycle, + .get_cycle_duration = cxl_patrol_scrub_read_scrub_cycle, + .set_cycle_duration = cxl_patrol_scrub_write_scrub_cycle, +}; + +static int cxl_memdev_scrub_init(struct cxl_memdev *cxlmd, + struct edac_dev_feature *ras_feature, + u8 scrub_inst) +{ + struct cxl_patrol_scrub_context *cxl_ps_ctx; + struct cxl_feat_entry *feat_entry; + + feat_entry = cxl_get_feature_entry(cxlmd->cxlds, + &CXL_FEAT_PATROL_SCRUB_UUID); + if (!feat_entry) + return -EOPNOTSUPP; + + if (!(le32_to_cpu(feat_entry->flags) & CXL_FEATURE_F_CHANGEABLE)) + return -EOPNOTSUPP; + + cxl_ps_ctx = devm_kzalloc(&cxlmd->dev, sizeof(*cxl_ps_ctx), GFP_KERNEL); + if (!cxl_ps_ctx) + return -ENOMEM; + + *cxl_ps_ctx = (struct cxl_patrol_scrub_context){ + .get_feat_size = le16_to_cpu(feat_entry->get_feat_size), + .set_feat_size = le16_to_cpu(feat_entry->set_feat_size), + .get_version = feat_entry->get_feat_ver, + .set_version = feat_entry->set_feat_ver, + .effects = le16_to_cpu(feat_entry->effects), + .instance = scrub_inst, + .cxlmd = cxlmd, + }; + + ras_feature->ft_type = RAS_FEAT_SCRUB; + ras_feature->instance = cxl_ps_ctx->instance; + ras_feature->scrub_ops = &cxl_ps_scrub_ops; + ras_feature->ctx = cxl_ps_ctx; + + return 0; +} + +static int cxl_region_scrub_init(struct cxl_region *cxlr, + struct edac_dev_feature *ras_feature, + u8 scrub_inst) +{ + struct cxl_patrol_scrub_context *cxl_ps_ctx; + struct cxl_region_params *p = &cxlr->params; + struct cxl_feat_entry *feat_entry = NULL; + struct cxl_memdev *cxlmd; + int i; + + /* + * The cxl_region_rwsem must be held if the code below is used in a context + * other than when the region is in the probe state, as shown here. + */ + for (i = 0; i < p->nr_targets; i++) { + struct cxl_endpoint_decoder *cxled = p->targets[i]; + + cxlmd = cxled_to_memdev(cxled); + feat_entry = cxl_get_feature_entry(cxlmd->cxlds, + &CXL_FEAT_PATROL_SCRUB_UUID); + if (!feat_entry) + return -EOPNOTSUPP; + + if (!(le32_to_cpu(feat_entry->flags) & + CXL_FEATURE_F_CHANGEABLE)) + return -EOPNOTSUPP; + } + + cxl_ps_ctx = devm_kzalloc(&cxlr->dev, sizeof(*cxl_ps_ctx), GFP_KERNEL); + if (!cxl_ps_ctx) + return -ENOMEM; + + *cxl_ps_ctx = (struct cxl_patrol_scrub_context){ + .get_feat_size = le16_to_cpu(feat_entry->get_feat_size), + .set_feat_size = le16_to_cpu(feat_entry->set_feat_size), + .get_version = feat_entry->get_feat_ver, + .set_version = feat_entry->set_feat_ver, + .effects = le16_to_cpu(feat_entry->effects), + .instance = scrub_inst, + .cxlr = cxlr, + }; + + ras_feature->ft_type = RAS_FEAT_SCRUB; + ras_feature->instance = cxl_ps_ctx->instance; + ras_feature->scrub_ops = &cxl_ps_scrub_ops; + ras_feature->ctx = cxl_ps_ctx; + + return 0; +} + +int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) +{ + struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; + int num_ras_features = 0; + u8 scrub_inst = 0; + int rc; + + rc = cxl_memdev_scrub_init(cxlmd, &ras_features[num_ras_features], + scrub_inst); + if (rc < 0 && rc != -EOPNOTSUPP) + return rc; + + if (rc != -EOPNOTSUPP) + num_ras_features++; + + char *cxl_dev_name __free(kfree) = + kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlmd->dev)); + + return edac_dev_register(&cxlmd->dev, cxl_dev_name, NULL, + num_ras_features, ras_features); +} +EXPORT_SYMBOL_NS_GPL(devm_cxl_memdev_edac_register, "CXL"); + +int devm_cxl_region_edac_register(struct cxl_region *cxlr) +{ + struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; + int num_ras_features = 0; + u8 scrub_inst = 0; + int rc; + + rc = cxl_region_scrub_init(cxlr, &ras_features[num_ras_features], + scrub_inst); + if (rc < 0) + return rc; + + num_ras_features++; + + char *cxl_dev_name __free(kfree) = + kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlr->dev)); + + return edac_dev_register(&cxlr->dev, cxl_dev_name, NULL, + num_ras_features, ras_features); +} +EXPORT_SYMBOL_NS_GPL(devm_cxl_region_edac_register, "CXL"); diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c index b3260d433ec7..2aa6eb675fdf 100644 --- a/drivers/cxl/core/region.c +++ b/drivers/cxl/core/region.c @@ -3542,6 +3542,11 @@ static int cxl_region_probe(struct device *dev) case CXL_PARTMODE_PMEM: return devm_cxl_add_pmem_region(cxlr); case CXL_PARTMODE_RAM: + rc = devm_cxl_region_edac_register(cxlr); + if (rc) + dev_dbg(&cxlr->dev, "CXL EDAC registration for region_id=%d failed\n", + cxlr->id); + /* * The region can not be manged by CXL if any portion of * it is already online as 'System RAM' diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index 3ec6b906371b..11fa98cc4d9c 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -853,6 +853,16 @@ int cxl_trigger_poison_list(struct cxl_memdev *cxlmd); int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa); int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa); +#if IS_ENABLED(CONFIG_CXL_EDAC_MEM_FEATURES) +int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd); +int devm_cxl_region_edac_register(struct cxl_region *cxlr); +#else +static inline int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) +{ return 0; } +static inline int devm_cxl_region_edac_register(struct cxl_region *cxlr) +{ return 0; } +#endif + #ifdef CONFIG_CXL_SUSPEND void cxl_mem_active_inc(void); void cxl_mem_active_dec(void); diff --git a/drivers/cxl/mem.c b/drivers/cxl/mem.c index 9675243bd05b..6e6777b7bafb 100644 --- a/drivers/cxl/mem.c +++ b/drivers/cxl/mem.c @@ -180,6 +180,10 @@ static int cxl_mem_probe(struct device *dev) return rc; } + rc = devm_cxl_memdev_edac_register(cxlmd); + if (rc) + dev_dbg(dev, "CXL memdev EDAC registration failed rc=%d\n", rc); + /* * The kernel may be operating out of CXL memory on this device, * there is no spec defined way to determine whether this device From patchwork Thu Mar 20 18:04:41 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024301 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CAA132288C0; Thu, 20 Mar 2025 18:05:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493943; cv=none; b=DNuPxg2YL0Ye3rdgxz2p2n4FNTeZzuxn5joqteyHZCoYWLu0ZOixuHOUgakd/kMYHAgIbC4YaJIJqPy0zsO5qg1lvMfQ63vEujgoW2LimCNQaBGUlUbR1Zp8gbdsmoBc5t9IA+MtQDSTsptqvsdnTjRZQcpZE/z96k7voTl+xi0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493943; c=relaxed/simple; bh=NYYXvZmu7N1U3fFV3semGfYseLiNbjTIuoHUk2V7rng=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Oo9aZmDNFy2/Dtgx86h0badGNiI0wTuxIiv2GJAKYlPI3XFWKWQwD5RnNnz3S9oXDqe1xli/Z/SbQ/0VB6ylisl7t3CLcBknphe9nQ56dc6frHyzyeCMI+vTC5A/rAzvVcdJ/K6kRPz+IUSt3Q64RbiX7E+5MiC3hQRvVo7+w8Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.216]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMP0HjHz6K9M7; Fri, 21 Mar 2025 02:02:41 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id 8C0A91405A0; Fri, 21 Mar 2025 02:05:39 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:37 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 4/8] cxl/edac: Add CXL memory device ECS control feature Date: Thu, 20 Mar 2025 18:04:41 +0000 Message-ID: <20250320180450.539-5-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose CXL spec 3.2 section 8.2.10.9.11.2 describes the DDR5 ECS (Error Check Scrub) control feature. The Error Check Scrub (ECS) is a feature defined in JEDEC DDR5 SDRAM Specification (JESD79-5) and allows the DRAM to internally read, correct single-bit errors, and write back corrected data bits to the DRAM array while providing transparency to error counts. The ECS control allows the requester to change the log entry type, the ECS threshold count (provided the request falls within the limits specified in DDR5 mode registers), switch between codeword mode and row count mode, and reset the ECS counter. Register with EDAC device driver, which retrieves the ECS attribute descriptors from the EDAC ECS and exposes the ECS control attributes to userspace via sysfs. For example, the ECS control for the memory media FRU0 in CXL mem0 device is located at /sys/bus/edac/devices/cxl_mem0/ecs_fru0/ Reviewed-by: Jonathan Cameron Reviewed-by: Fan Ni Signed-off-by: Shiju Jose --- drivers/cxl/Kconfig | 1 + drivers/cxl/core/edac.c | 353 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 353 insertions(+), 1 deletion(-) diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig index b5ede1308425..1c67bf844993 100644 --- a/drivers/cxl/Kconfig +++ b/drivers/cxl/Kconfig @@ -120,6 +120,7 @@ config CXL_EDAC_MEM_FEATURES depends on CXL_FEATURES depends on EDAC >= CXL_BUS depends on EDAC_SCRUB + depends on EDAC_ECS help The CXL EDAC memory feature control is optional and allows host to control the EDAC memory features configurations of CXL memory diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c index 5ec3535785e1..1110685ed41a 100644 --- a/drivers/cxl/core/edac.c +++ b/drivers/cxl/core/edac.c @@ -19,7 +19,7 @@ #include #include "core.h" -#define CXL_NR_EDAC_DEV_FEATURES 1 +#define CXL_NR_EDAC_DEV_FEATURES 2 static struct rw_semaphore *cxl_acquire(struct rw_semaphore *rwsem) { @@ -428,6 +428,350 @@ static int cxl_region_scrub_init(struct cxl_region *cxlr, return 0; } +/* + * CXL DDR5 ECS control definitions. + */ +struct cxl_ecs_context { + u16 num_media_frus; + u16 get_feat_size; + u16 set_feat_size; + u8 get_version; + u8 set_version; + u16 effects; + struct cxl_memdev *cxlmd; +}; + +enum { + CXL_ECS_PARAM_LOG_ENTRY_TYPE, + CXL_ECS_PARAM_THRESHOLD, + CXL_ECS_PARAM_MODE, + CXL_ECS_PARAM_RESET_COUNTER, +}; + +#define CXL_ECS_LOG_ENTRY_TYPE_MASK GENMASK(1, 0) +#define CXL_ECS_REALTIME_REPORT_CAP_MASK BIT(0) +#define CXL_ECS_THRESHOLD_COUNT_MASK GENMASK(2, 0) +#define CXL_ECS_COUNT_MODE_MASK BIT(3) +#define CXL_ECS_RESET_COUNTER_MASK BIT(4) +#define CXL_ECS_RESET_COUNTER 1 + +enum { + ECS_THRESHOLD_256 = 256, + ECS_THRESHOLD_1024 = 1024, + ECS_THRESHOLD_4096 = 4096, +}; + +enum { + ECS_THRESHOLD_IDX_256 = 3, + ECS_THRESHOLD_IDX_1024 = 4, + ECS_THRESHOLD_IDX_4096 = 5, +}; + +static const u16 ecs_supp_threshold[] = { + [ECS_THRESHOLD_IDX_256] = 256, + [ECS_THRESHOLD_IDX_1024] = 1024, + [ECS_THRESHOLD_IDX_4096] = 4096, +}; + +enum { + ECS_LOG_ENTRY_TYPE_DRAM = 0x0, + ECS_LOG_ENTRY_TYPE_MEM_MEDIA_FRU = 0x1, +}; + +enum cxl_ecs_count_mode { + ECS_MODE_COUNTS_ROWS = 0, + ECS_MODE_COUNTS_CODEWORDS = 1, +}; + +/** + * struct cxl_ecs_params - CXL memory DDR5 ECS parameter data structure. + * @threshold: ECS threshold count per GB of memory cells. + * @log_entry_type: ECS log entry type, per DRAM or per memory media FRU. + * @reset_counter: [IN] reset ECC counter to default value. + * @count_mode: codeword/row count mode + * 0 : ECS counts rows with errors + * 1 : ECS counts codeword with errors + */ +struct cxl_ecs_params { + u16 threshold; + u8 log_entry_type; + u8 reset_counter; + enum cxl_ecs_count_mode count_mode; +}; + +/* + * See CXL spec rev 3.2 @8.2.10.9.11.2 Table 8-225 DDR5 ECS Control Feature + * Readable Attributes. + */ +struct cxl_ecs_fru_rd_attrs { + u8 ecs_cap; + __le16 ecs_config; + u8 ecs_flags; +} __packed; + +struct cxl_ecs_rd_attrs { + u8 ecs_log_cap; + struct cxl_ecs_fru_rd_attrs fru_attrs[]; +} __packed; + +/* + * See CXL spec rev 3.2 @8.2.10.9.11.2 Table 8-226 DDR5 ECS Control Feature + * Writable Attributes. + */ +struct cxl_ecs_fru_wr_attrs { + __le16 ecs_config; +} __packed; + +struct cxl_ecs_wr_attrs { + u8 ecs_log_cap; + struct cxl_ecs_fru_wr_attrs fru_attrs[]; +} __packed; + +/* + * CXL DDR5 ECS control functions. + */ +static int cxl_mem_ecs_get_attrs(struct device *dev, + struct cxl_ecs_context *cxl_ecs_ctx, + int fru_id, struct cxl_ecs_params *params) +{ + struct cxl_memdev *cxlmd = cxl_ecs_ctx->cxlmd; + struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox; + struct cxl_ecs_fru_rd_attrs *fru_rd_attrs; + size_t rd_data_size; + u8 threshold_index; + size_t data_size; + u16 ecs_config; + + rd_data_size = cxl_ecs_ctx->get_feat_size; + + struct cxl_ecs_rd_attrs *rd_attrs __free(kvfree) = + kvzalloc(rd_data_size, GFP_KERNEL); + if (!rd_attrs) + return -ENOMEM; + + params->log_entry_type = 0; + params->threshold = 0; + params->count_mode = 0; + data_size = cxl_get_feature(cxl_mbox, &CXL_FEAT_ECS_UUID, + CXL_GET_FEAT_SEL_CURRENT_VALUE, rd_attrs, + rd_data_size, 0, NULL); + if (!data_size) + return -EIO; + + fru_rd_attrs = rd_attrs->fru_attrs; + params->log_entry_type = + FIELD_GET(CXL_ECS_LOG_ENTRY_TYPE_MASK, rd_attrs->ecs_log_cap); + ecs_config = le16_to_cpu(fru_rd_attrs[fru_id].ecs_config); + threshold_index = FIELD_GET(CXL_ECS_THRESHOLD_COUNT_MASK, ecs_config); + params->threshold = ecs_supp_threshold[threshold_index]; + params->count_mode = FIELD_GET(CXL_ECS_COUNT_MODE_MASK, ecs_config); + return 0; +} + +static int cxl_mem_ecs_set_attrs(struct device *dev, + struct cxl_ecs_context *cxl_ecs_ctx, + int fru_id, struct cxl_ecs_params *params, + u8 param_type) +{ + struct cxl_memdev *cxlmd = cxl_ecs_ctx->cxlmd; + struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox; + struct cxl_ecs_fru_rd_attrs *fru_rd_attrs; + struct cxl_ecs_fru_wr_attrs *fru_wr_attrs; + size_t rd_data_size, wr_data_size; + u16 num_media_frus, count; + size_t data_size; + u16 ecs_config; + + num_media_frus = cxl_ecs_ctx->num_media_frus; + rd_data_size = cxl_ecs_ctx->get_feat_size; + wr_data_size = cxl_ecs_ctx->set_feat_size; + struct cxl_ecs_rd_attrs *rd_attrs __free(kvfree) = + kvzalloc(rd_data_size, GFP_KERNEL); + if (!rd_attrs) + return -ENOMEM; + + data_size = cxl_get_feature(cxl_mbox, &CXL_FEAT_ECS_UUID, + CXL_GET_FEAT_SEL_CURRENT_VALUE, rd_attrs, + rd_data_size, 0, NULL); + if (!data_size) + return -EIO; + + struct cxl_ecs_wr_attrs *wr_attrs __free(kvfree) = + kvzalloc(wr_data_size, GFP_KERNEL); + if (!wr_attrs) + return -ENOMEM; + + /* + * Fill writable attributes from the current attributes read + * for all the media FRUs. + */ + fru_rd_attrs = rd_attrs->fru_attrs; + fru_wr_attrs = wr_attrs->fru_attrs; + wr_attrs->ecs_log_cap = rd_attrs->ecs_log_cap; + for (count = 0; count < num_media_frus; count++) + fru_wr_attrs[count].ecs_config = fru_rd_attrs[count].ecs_config; + + /* Fill attribute to be set for the media FRU */ + ecs_config = le16_to_cpu(fru_rd_attrs[fru_id].ecs_config); + switch (param_type) { + case CXL_ECS_PARAM_LOG_ENTRY_TYPE: + if (params->log_entry_type != ECS_LOG_ENTRY_TYPE_DRAM && + params->log_entry_type != ECS_LOG_ENTRY_TYPE_MEM_MEDIA_FRU) + return -EINVAL; + + wr_attrs->ecs_log_cap = FIELD_PREP(CXL_ECS_LOG_ENTRY_TYPE_MASK, + params->log_entry_type); + break; + case CXL_ECS_PARAM_THRESHOLD: + ecs_config &= ~CXL_ECS_THRESHOLD_COUNT_MASK; + switch (params->threshold) { + case ECS_THRESHOLD_256: + ecs_config |= FIELD_PREP(CXL_ECS_THRESHOLD_COUNT_MASK, + ECS_THRESHOLD_IDX_256); + break; + case ECS_THRESHOLD_1024: + ecs_config |= FIELD_PREP(CXL_ECS_THRESHOLD_COUNT_MASK, + ECS_THRESHOLD_IDX_1024); + break; + case ECS_THRESHOLD_4096: + ecs_config |= FIELD_PREP(CXL_ECS_THRESHOLD_COUNT_MASK, + ECS_THRESHOLD_IDX_4096); + break; + default: + dev_dbg(dev, + "Invalid CXL ECS scrub threshold count(%d) to set\n", + params->threshold); + dev_dbg(dev, + "Supported scrub threshold counts: %u, %u, %u\n", + ECS_THRESHOLD_256, ECS_THRESHOLD_1024, + ECS_THRESHOLD_4096); + return -EINVAL; + } + break; + case CXL_ECS_PARAM_MODE: + if (params->count_mode != ECS_MODE_COUNTS_ROWS && + params->count_mode != ECS_MODE_COUNTS_CODEWORDS) { + dev_dbg(dev, "Invalid CXL ECS scrub mode(%d) to set\n", + params->count_mode); + dev_dbg(dev, + "Supported ECS Modes: 0: ECS counts rows with errors," + " 1: ECS counts codewords with errors\n"); + return -EINVAL; + } + ecs_config &= ~CXL_ECS_COUNT_MODE_MASK; + ecs_config |= + FIELD_PREP(CXL_ECS_COUNT_MODE_MASK, params->count_mode); + break; + case CXL_ECS_PARAM_RESET_COUNTER: + if (params->reset_counter != CXL_ECS_RESET_COUNTER) + return -EINVAL; + + ecs_config &= ~CXL_ECS_RESET_COUNTER_MASK; + ecs_config |= FIELD_PREP(CXL_ECS_RESET_COUNTER_MASK, + params->reset_counter); + break; + default: + return -EINVAL; + } + fru_wr_attrs[fru_id].ecs_config = cpu_to_le16(ecs_config); + + return cxl_set_feature(cxl_mbox, &CXL_FEAT_ECS_UUID, + cxl_ecs_ctx->set_version, wr_attrs, wr_data_size, + CXL_SET_FEAT_FLAG_DATA_SAVED_ACROSS_RESET, 0, + NULL); +} + +#define CXL_ECS_GET_ATTR(attrib) \ + static int cxl_ecs_get_##attrib(struct device *dev, void *drv_data, \ + int fru_id, u32 *val) \ + { \ + struct cxl_ecs_context *ctx = drv_data; \ + struct cxl_ecs_params params; \ + int ret; \ + \ + ret = cxl_mem_ecs_get_attrs(dev, ctx, fru_id, ¶ms); \ + if (ret) \ + return ret; \ + \ + *val = params.attrib; \ + \ + return 0; \ + } + +CXL_ECS_GET_ATTR(log_entry_type) +CXL_ECS_GET_ATTR(count_mode) +CXL_ECS_GET_ATTR(threshold) + +#define CXL_ECS_SET_ATTR(attrib, param_type) \ + static int cxl_ecs_set_##attrib(struct device *dev, void *drv_data, \ + int fru_id, u32 val) \ + { \ + struct cxl_ecs_context *ctx = drv_data; \ + struct cxl_ecs_params params = { \ + .attrib = val, \ + }; \ + \ + return cxl_mem_ecs_set_attrs(dev, ctx, fru_id, ¶ms, \ + (param_type)); \ + } +CXL_ECS_SET_ATTR(log_entry_type, CXL_ECS_PARAM_LOG_ENTRY_TYPE) +CXL_ECS_SET_ATTR(count_mode, CXL_ECS_PARAM_MODE) +CXL_ECS_SET_ATTR(reset_counter, CXL_ECS_PARAM_RESET_COUNTER) +CXL_ECS_SET_ATTR(threshold, CXL_ECS_PARAM_THRESHOLD) + +static const struct edac_ecs_ops cxl_ecs_ops = { + .get_log_entry_type = cxl_ecs_get_log_entry_type, + .set_log_entry_type = cxl_ecs_set_log_entry_type, + .get_mode = cxl_ecs_get_count_mode, + .set_mode = cxl_ecs_set_count_mode, + .reset = cxl_ecs_set_reset_counter, + .get_threshold = cxl_ecs_get_threshold, + .set_threshold = cxl_ecs_set_threshold, +}; + +static int cxl_memdev_ecs_init(struct cxl_memdev *cxlmd, + struct edac_dev_feature *ras_feature) +{ + struct cxl_ecs_context *cxl_ecs_ctx; + struct cxl_feat_entry *feat_entry; + int num_media_frus; + + feat_entry = cxl_get_feature_entry(cxlmd->cxlds, &CXL_FEAT_ECS_UUID); + if (!feat_entry) + return -EOPNOTSUPP; + + if (!(le32_to_cpu(feat_entry->flags) & CXL_FEATURE_F_CHANGEABLE)) + return -EOPNOTSUPP; + + num_media_frus = (le16_to_cpu(feat_entry->get_feat_size) - + sizeof(struct cxl_ecs_rd_attrs)) / + sizeof(struct cxl_ecs_fru_rd_attrs); + if (!num_media_frus) + return -EOPNOTSUPP; + + cxl_ecs_ctx = + devm_kzalloc(&cxlmd->dev, sizeof(*cxl_ecs_ctx), GFP_KERNEL); + if (!cxl_ecs_ctx) + return -ENOMEM; + + *cxl_ecs_ctx = (struct cxl_ecs_context){ + .get_feat_size = le16_to_cpu(feat_entry->get_feat_size), + .set_feat_size = le16_to_cpu(feat_entry->set_feat_size), + .get_version = feat_entry->get_feat_ver, + .set_version = feat_entry->set_feat_ver, + .effects = le16_to_cpu(feat_entry->effects), + .num_media_frus = num_media_frus, + .cxlmd = cxlmd, + }; + + ras_feature->ft_type = RAS_FEAT_ECS; + ras_feature->ecs_ops = &cxl_ecs_ops; + ras_feature->ctx = cxl_ecs_ctx; + ras_feature->ecs_info.num_media_frus = num_media_frus; + + return 0; +} + int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) { struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; @@ -443,6 +787,13 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) if (rc != -EOPNOTSUPP) num_ras_features++; + rc = cxl_memdev_ecs_init(cxlmd, &ras_features[num_ras_features]); + if (rc < 0 && rc != -EOPNOTSUPP) + return rc; + + if (rc != -EOPNOTSUPP) + num_ras_features++; + char *cxl_dev_name __free(kfree) = kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlmd->dev)); From patchwork Thu Mar 20 18:04:42 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024302 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF5E0226CE5; Thu, 20 Mar 2025 18:05:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493946; cv=none; b=c1Z55ZRnPT4AXQFVeSFXPN8IBnDN0f9eGStu7j/y8b4z/mbWAKOaYXgciL1WtN3QG1KIs7oMQuxQc9mXDizGQeI8WtlvV2ibpHhGBazRfL3ug7/35EoVh9Ykj0f486zuk3ZvPPo82MpCkCan90K4TPi78TdfEHx3ZdaLoF/Ir/g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493946; c=relaxed/simple; bh=o+yL9tkFoVdL13trPIrqDxF2bvZz2EoXjvJ1ME/Cw84=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YPiSqhQgdaJASaZht4B9SmDc6VUCvQFeIPPX9PrqfNw0PUCf1T+Dlyb+9wWSK7SVZFgvI+EI1dIuwizqcy6Nj5YBEGH8sJWVJrlEx9vPod1R+a1frsIhGtNgrhRxmYQZS63QmCXmBP5RrNe0vnYFFK6nk8kI12n+6k6V45yC6rM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.216]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMR6rbcz6K9Lg; Fri, 21 Mar 2025 02:02:43 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id 794921405A0; Fri, 21 Mar 2025 02:05:42 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:40 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 5/8] cxl/mbox: Add support for PERFORM_MAINTENANCE mailbox command Date: Thu, 20 Mar 2025 18:04:42 +0000 Message-ID: <20250320180450.539-6-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Add support for PERFORM_MAINTENANCE mailbox command. CXL spec 3.2 section 8.2.10.7.1 describes the Perform Maintenance command. This command requests the device to execute the maintenance operation specified by the maintenance operation class and the maintenance operation subclass. Reviewed-by: Jonathan Cameron Reviewed-by: Dave Jiang Signed-off-by: Shiju Jose --- drivers/cxl/core/mbox.c | 34 ++++++++++++++++++++++++++++++++++ drivers/cxl/cxlmem.h | 17 +++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index d72764056ce6..19d46a284650 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -824,6 +824,40 @@ static const uuid_t log_uuid[] = { [VENDOR_DEBUG_UUID] = DEFINE_CXL_VENDOR_DEBUG_UUID, }; +int cxl_do_maintenance(struct cxl_mailbox *cxl_mbox, + u8 class, u8 subclass, + void *data_in, size_t data_in_size) +{ + struct cxl_memdev_maintenance_pi { + struct cxl_mbox_do_maintenance_hdr hdr; + u8 data[]; + } __packed; + struct cxl_mbox_cmd mbox_cmd; + size_t hdr_size; + + struct cxl_memdev_maintenance_pi *pi __free(kfree) = + kmalloc(cxl_mbox->payload_size, GFP_KERNEL); + pi->hdr.op_class = class; + pi->hdr.op_subclass = subclass; + hdr_size = sizeof(pi->hdr); + /* + * Check minimum mbox payload size is available for + * the maintenance data transfer. + */ + if (hdr_size + data_in_size > cxl_mbox->payload_size) + return -ENOMEM; + + memcpy(pi->data, data_in, data_in_size); + mbox_cmd = (struct cxl_mbox_cmd) { + .opcode = CXL_MBOX_OP_DO_MAINTENANCE, + .size_in = hdr_size + data_in_size, + .payload_in = pi, + }; + + return cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); +} +EXPORT_SYMBOL_NS_GPL(cxl_do_maintenance, "CXL"); + /** * cxl_enumerate_cmds() - Enumerate commands for a device. * @mds: The driver data for the operation diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index 11fa98cc4d9c..7ab257e0c85e 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -527,6 +527,7 @@ enum cxl_opcode { CXL_MBOX_OP_GET_SUPPORTED_FEATURES = 0x0500, CXL_MBOX_OP_GET_FEATURE = 0x0501, CXL_MBOX_OP_SET_FEATURE = 0x0502, + CXL_MBOX_OP_DO_MAINTENANCE = 0x0600, CXL_MBOX_OP_IDENTIFY = 0x4000, CXL_MBOX_OP_GET_PARTITION_INFO = 0x4100, CXL_MBOX_OP_SET_PARTITION_INFO = 0x4101, @@ -827,6 +828,19 @@ enum { CXL_PMEM_SEC_PASS_USER, }; +/* + * Perform Maintenance CXL 3.2 Spec 8.2.10.7.1 + */ + +/* + * Perform Maintenance input payload + * CXL rev 3.2 section 8.2.10.7.1 Table 8-117 + */ +struct cxl_mbox_do_maintenance_hdr { + u8 op_class; + u8 op_subclass; +} __packed; + int cxl_internal_send_cmd(struct cxl_mailbox *cxl_mbox, struct cxl_mbox_cmd *cmd); int cxl_dev_state_identify(struct cxl_memdev_state *mds); @@ -898,4 +912,7 @@ struct cxl_hdm { struct seq_file; struct dentry *cxl_debugfs_create_dir(const char *dir); void cxl_dpa_debug(struct seq_file *file, struct cxl_dev_state *cxlds); +int cxl_do_maintenance(struct cxl_mailbox *cxl_mbox, + u8 class, u8 subclass, + void *data_in, size_t data_in_size); #endif /* __CXL_MEM_H__ */ From patchwork Thu Mar 20 18:04:43 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024303 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 74029226D08; Thu, 20 Mar 2025 18:05:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493949; cv=none; b=qBP5AaYs9CQ0IeOuCfZrqr08V5HJGtM2irti+daKAtNWtKMj+ZJYBh1aYDSzuwZbV3SxknGR9rif43tEhNnQZorZNGR7LErkmU4zWgkq78K3SM9uWGEFwoauF4nojTVysqaThxlS0XvEzvdwGEwhts6tKZqzDjcZ+5iFsxfyRD0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493949; c=relaxed/simple; bh=rvzK9EISyRVZGk9AkD7d1E/WfLOQ2d52vpl/ioSZjvc=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=PE/jy9669f3RGA51/lN5GWY3+gb4A1N/S6DkJNkTQp/BHiOmKirYaH5s69USzohHSsaJnPh7kE2qJHTIr4VcaI8YgBvuktV/UiW032ZzNQppPJfhnI9oN2EWZ1ubF6eUhNGa0gXRKTMKwUMv7u8zigAj4epgms8ydcS6iQrvh/g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.216]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMV5w66z6K5Yr; Fri, 21 Mar 2025 02:02:46 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id 595351405A0; Fri, 21 Mar 2025 02:05:45 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:43 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 6/8] cxl: Support for finding memory operation attributes from the current boot Date: Thu, 20 Mar 2025 18:04:43 +0000 Message-ID: <20250320180450.539-7-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Certain operations on memory, such as memory repair, are permitted only when the address and other attributes for the operation are from the current boot. This is determined by checking whether the memory attributes for the operation match those in the CXL gen_media or CXL DRAM memory event records reported during the current boot. The CXL event records must be backed up because they are cleared in the hardware after being processed by the kernel. Support is added for storing CXL gen_media or CXL DRAM memory event records in xarrays. Additionally, helper functions are implemented to find a matching record in the xarray storage based on the memory attributes and repair type. Add validity check, when matching attributes for sparing, using the validity flag in the DRAM event record, to ensure that all required attributes for a requested repair operation are valid and set. Co-developed-by: Jonathan Cameron Signed-off-by: Jonathan Cameron Signed-off-by: Shiju Jose --- drivers/cxl/core/mbox.c | 11 ++- drivers/cxl/core/memdev.c | 9 +++ drivers/cxl/core/ras.c | 145 ++++++++++++++++++++++++++++++++++++++ drivers/cxl/cxlmem.h | 46 ++++++++++++ drivers/cxl/pci.c | 3 + 5 files changed, 212 insertions(+), 2 deletions(-) diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 19d46a284650..c9328f1b6464 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -956,12 +956,19 @@ void cxl_event_trace_record(const struct cxl_memdev *cxlmd, hpa_alias = hpa - cache_size; } - if (event_type == CXL_CPER_EVENT_GEN_MEDIA) + if (event_type == CXL_CPER_EVENT_GEN_MEDIA) { + if (cxl_store_rec_gen_media((struct cxl_memdev *)cxlmd, evt)) + dev_dbg(&cxlmd->dev, "CXL store rec_gen_media failed\n"); + trace_cxl_general_media(cxlmd, type, cxlr, hpa, hpa_alias, &evt->gen_media); - else if (event_type == CXL_CPER_EVENT_DRAM) + } else if (event_type == CXL_CPER_EVENT_DRAM) { + if (cxl_store_rec_dram((struct cxl_memdev *)cxlmd, evt)) + dev_dbg(&cxlmd->dev, "CXL store rec_dram failed\n"); + trace_cxl_dram(cxlmd, type, cxlr, hpa, hpa_alias, &evt->dram); + } } } EXPORT_SYMBOL_NS_GPL(cxl_event_trace_record, "CXL"); diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index a16a5886d40a..bd9ba50bc01e 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -25,8 +25,17 @@ static DEFINE_IDA(cxl_memdev_ida); static void cxl_memdev_release(struct device *dev) { struct cxl_memdev *cxlmd = to_cxl_memdev(dev); + struct cxl_event_gen_media *rec_gen_media; + struct cxl_event_dram *rec_dram; + unsigned long index; ida_free(&cxl_memdev_ida, cxlmd->id); + xa_for_each(&cxlmd->rec_dram, index, rec_dram) + kfree(rec_dram); + xa_destroy(&cxlmd->rec_dram); + xa_for_each(&cxlmd->rec_gen_media, index, rec_gen_media) + kfree(rec_gen_media); + xa_destroy(&cxlmd->rec_gen_media); kfree(cxlmd); } diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index 485a831695c7..c703d4e7e05b 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -7,6 +7,151 @@ #include #include "trace.h" +struct cxl_event_gen_media * +cxl_find_rec_gen_media(struct cxl_memdev *cxlmd, + struct cxl_mem_repair_attrbs *attrbs) +{ + struct cxl_event_gen_media *rec; + + rec = xa_load(&cxlmd->rec_gen_media, attrbs->dpa); + if (!rec) + return NULL; + + if (attrbs->repair_type == CXL_PPR) + return rec; + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(cxl_find_rec_gen_media, "CXL"); + +struct cxl_event_dram *cxl_find_rec_dram(struct cxl_memdev *cxlmd, + struct cxl_mem_repair_attrbs *attrbs) +{ + struct cxl_event_dram *rec; + u16 validity_flags; + + rec = xa_load(&cxlmd->rec_dram, attrbs->dpa); + if (!rec) + return NULL; + + validity_flags = get_unaligned_le16(rec->media_hdr.validity_flags); + if (!(validity_flags & CXL_DER_VALID_CHANNEL) || + !(validity_flags & CXL_DER_VALID_RANK)) + return NULL; + + switch (attrbs->repair_type) { + case CXL_PPR: + if (!(validity_flags & CXL_DER_VALID_NIBBLE) || + get_unaligned_le24(rec->nibble_mask) == attrbs->nibble_mask) + return rec; + break; + case CXL_CACHELINE_SPARING: + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) || + !(validity_flags & CXL_DER_VALID_BANK) || + !(validity_flags & CXL_DER_VALID_ROW) || + !(validity_flags & CXL_DER_VALID_COLUMN)) + return NULL; + + if (rec->media_hdr.channel == attrbs->channel && + rec->media_hdr.rank == attrbs->rank && + rec->bank_group == attrbs->bank_group && + rec->bank == attrbs->bank && + get_unaligned_le24(rec->row) == attrbs->row && + get_unaligned_le16(rec->column) == attrbs->column && + (!(validity_flags & CXL_DER_VALID_NIBBLE) || + get_unaligned_le24(rec->nibble_mask) == + attrbs->nibble_mask) && + (!(validity_flags & CXL_DER_VALID_SUB_CHANNEL) || + rec->sub_channel == attrbs->sub_channel)) + return rec; + break; + case CXL_ROW_SPARING: + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) || + !(validity_flags & CXL_DER_VALID_BANK) || + !(validity_flags & CXL_DER_VALID_ROW)) + return NULL; + + if (rec->media_hdr.channel == attrbs->channel && + rec->media_hdr.rank == attrbs->rank && + rec->bank_group == attrbs->bank_group && + rec->bank == attrbs->bank && + get_unaligned_le24(rec->row) == attrbs->row && + (!(validity_flags & CXL_DER_VALID_NIBBLE) || + get_unaligned_le24(rec->nibble_mask) == + attrbs->nibble_mask)) + return rec; + break; + case CXL_BANK_SPARING: + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) || + !(validity_flags & CXL_DER_VALID_BANK)) + return NULL; + + if (rec->media_hdr.channel == attrbs->channel && + rec->media_hdr.rank == attrbs->rank && + rec->bank_group == attrbs->bank_group && + rec->bank == attrbs->bank && + (!(validity_flags & CXL_DER_VALID_NIBBLE) || + get_unaligned_le24(rec->nibble_mask) == + attrbs->nibble_mask)) + return rec; + break; + case CXL_RANK_SPARING: + if (rec->media_hdr.channel == attrbs->channel && + rec->media_hdr.rank == attrbs->rank && + (!(validity_flags & CXL_DER_VALID_NIBBLE) || + get_unaligned_le24(rec->nibble_mask) == + attrbs->nibble_mask)) + return rec; + break; + default: + return NULL; + } + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(cxl_find_rec_dram, "CXL"); + +int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt) +{ + void *old_rec; + struct cxl_event_gen_media *rec = + kmemdup(&evt->gen_media, sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + + old_rec = xa_store(&cxlmd->rec_gen_media, + le64_to_cpu(rec->media_hdr.phys_addr), rec, + GFP_KERNEL); + if (xa_is_err(old_rec)) + return xa_err(old_rec); + + kfree(old_rec); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_store_rec_gen_media, "CXL"); + +int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt) +{ + void *old_rec; + struct cxl_event_dram *rec = + kmemdup(&evt->dram, sizeof(*rec), GFP_KERNEL); + + if (!rec) + return -ENOMEM; + + old_rec = xa_store(&cxlmd->rec_dram, + le64_to_cpu(rec->media_hdr.phys_addr), rec, + GFP_KERNEL); + if (xa_is_err(old_rec)) + return xa_err(old_rec); + + kfree(old_rec); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_store_rec_dram, "CXL"); + static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev, struct cxl_ras_capability_regs ras_cap) { diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index 7ab257e0c85e..24ece579a145 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -34,6 +34,41 @@ (FIELD_GET(CXLMDEV_RESET_NEEDED_MASK, status) != \ CXLMDEV_RESET_NEEDED_NOT) +enum cxl_mem_repair_type { + CXL_PPR, + CXL_CACHELINE_SPARING, + CXL_ROW_SPARING, + CXL_BANK_SPARING, + CXL_RANK_SPARING, + CXL_REPAIR_MAX, +}; + +/** + * struct cxl_mem_repair_attrbs - CXL memory repair attributes + * @dpa: DPA of memory to repair + * @nibble_mask: nibble mask, identifies one or more nibbles on the memory bus + * @row: row of memory to repair + * @column: column of memory to repair + * @channel: channel of memory to repair + * @sub_channel: sub channel of memory to repair + * @rank: rank of memory to repair + * @bank_group: bank group of memory to repair + * @bank: bank of memory to repair + * @repair_type: repair type. For eg. PPR, memory sparing etc. + */ +struct cxl_mem_repair_attrbs { + u64 dpa; + u32 nibble_mask; + u32 row; + u16 column; + u8 channel; + u8 sub_channel; + u8 rank; + u8 bank_group; + u8 bank; + enum cxl_mem_repair_type repair_type; +}; + /** * struct cxl_memdev - CXL bus object representing a Type-3 Memory Device * @dev: driver core device object @@ -45,6 +80,8 @@ * @endpoint: connection to the CXL port topology for this memory device * @id: id number of this memdev instance. * @depth: endpoint port depth + * @rec_gen_media: xarray to store CXL general media records + * @rec_dram: xarray to store CXL DRAM records */ struct cxl_memdev { struct device dev; @@ -56,6 +93,8 @@ struct cxl_memdev { struct cxl_port *endpoint; int id; int depth; + struct xarray rec_gen_media; + struct xarray rec_dram; }; static inline struct cxl_memdev *to_cxl_memdev(struct device *dev) @@ -877,6 +916,13 @@ static inline int devm_cxl_region_edac_register(struct cxl_region *cxlr) { return 0; } #endif +struct cxl_event_gen_media * +cxl_find_rec_gen_media(struct cxl_memdev *cxlmd, struct cxl_mem_repair_attrbs *attrbs); +struct cxl_event_dram *cxl_find_rec_dram(struct cxl_memdev *cxlmd, + struct cxl_mem_repair_attrbs *attrbs); +int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt); +int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt); + #ifdef CONFIG_CXL_SUSPEND void cxl_mem_active_inc(void); void cxl_mem_active_dec(void); diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 4288f4814cc5..51f09e685dd9 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -1053,6 +1053,9 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_save_state(pdev); + xa_init(&cxlmd->rec_gen_media); + xa_init(&cxlmd->rec_dram); + return rc; } From patchwork Thu Mar 20 18:04:44 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024304 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23C9E226D1B; Thu, 20 Mar 2025 18:05:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493952; cv=none; b=cSX/4un1YERVY48h2TxnnBbn/8Q1+1yuOmLNTPHX2y9zIDh1YCXBKrU7j8mJ9SzKFN8gzKqbxPJzwPclsKILKnet1iUqTnaUjBg2fSiZw21E8CgcYtViUIVl1ZsRHjHd8yF/+vh2+SLKXizp0w764ll5xnl4RC/UPOKJwkrqLZ8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493952; c=relaxed/simple; bh=wt67Gg1Nynd5YGqWjP/wZ6BxoH/PMxLa+gxNB+tjbi0=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=CWcP5TPM7ypMO+Z8/Ol1tJvSYBrjc6e6VumbedFjQf6bp481Y2G61ykCiSNljxJrESbC5Fi990NMW5lyPQJO5fyjIU0jJdGtN6MFWcGaD3eoL6vi24CQpVYLYnEIMfJcMxGHD+V9zyhsublUuYmsXYgNPfzVKxB3c5rlzoAvFlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.231]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMY5MQfz6K9HJ; Fri, 21 Mar 2025 02:02:49 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id 45C2A140856; Fri, 21 Mar 2025 02:05:48 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:46 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 7/8] cxl/memfeature: Add CXL memory device soft PPR control feature Date: Thu, 20 Mar 2025 18:04:44 +0000 Message-ID: <20250320180450.539-8-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Post Package Repair (PPR) maintenance operations may be supported by CXL devices that implement CXL.mem protocol. A PPR maintenance operation requests the CXL device to perform a repair operation on its media. For example, a CXL device with DRAM components that support PPR features may implement PPR Maintenance operations. DRAM components may support two types of PPR, hard PPR (hPPR), for a permanent row repair, and Soft PPR (sPPR), for a temporary row repair. Soft PPR is much faster than hPPR, but the repair is lost with a power cycle. During the execution of a PPR Maintenance operation, a CXL memory device: - May or may not retain data - May or may not be able to process CXL.mem requests correctly, including the ones that target the DPA involved in the repair. These CXL Memory Device capabilities are specified by Restriction Flags in the sPPR Feature and hPPR Feature. Soft PPR maintenance operation may be executed at runtime, if data is retained and CXL.mem requests are correctly processed. For CXL devices with DRAM components, hPPR maintenance operation may be executed only at boot because typically data may not be retained with hPPR maintenance operation. When a CXL device identifies error on a memory component, the device may inform the host about the need for a PPR maintenance operation by using an Event Record, where the Maintenance Needed flag is set. The Event Record specifies the DPA that should be repaired. A CXL device may not keep track of the requests that have already been sent and the information on which DPA should be repaired may be lost upon power cycle. The userspace tool requests for maintenance operation if the number of corrected error reported on a CXL.mem media exceeds error threshold. CXL spec 3.2 section 8.2.10.7.1.2 describes the device's sPPR (soft PPR) maintenance operation and section 8.2.10.7.1.3 describes the device's hPPR (hard PPR) maintenance operation feature. CXL spec 3.2 section 8.2.10.7.2.1 describes the sPPR feature discovery and configuration. CXL spec 3.2 section 8.2.10.7.2.2 describes the hPPR feature discovery and configuration. Add support for controlling CXL memory device soft PPR (sPPR) feature. Register with EDAC driver, which gets the memory repair attr descriptors from the EDAC memory repair driver and exposes sysfs repair control attributes for PRR to the userspace. For example CXL PPR control for the CXL mem0 device is exposed in /sys/bus/edac/devices/cxl_mem0/mem_repairX/ Add checks to ensure the memory to be repaired is offline and originates from a CXL DRAM or CXL gen_media error record reported in the current boot, before requesting a PPR operation on the device. Tested with QEMU patch for CXL PPR feature. https://lore.kernel.org/all/20240730045722.71482-1-dave@stgolabs.net/ Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron Signed-off-by: Shiju Jose --- Documentation/edac/memory_repair.rst | 18 ++ drivers/cxl/Kconfig | 1 + drivers/cxl/core/edac.c | 361 ++++++++++++++++++++++++++- drivers/edac/mem_repair.c | 5 + include/linux/edac.h | 3 + 5 files changed, 387 insertions(+), 1 deletion(-) diff --git a/Documentation/edac/memory_repair.rst b/Documentation/edac/memory_repair.rst index 52162a422864..cb0d91ccebfd 100644 --- a/Documentation/edac/memory_repair.rst +++ b/Documentation/edac/memory_repair.rst @@ -119,3 +119,21 @@ sysfs Sysfs files are documented in `Documentation/ABI/testing/sysfs-edac-memory-repair`. + +Examples +-------- + +The memory repair usage takes the form shown in this example: + +1. CXL memory device Soft Post Package Repair (Soft PPR) + +Post Package Repair (PPR) maintenance operations may be supported by CXL +devices that implement CXL.mem protocol. A PPR maintenance operation +requests the CXL device to perform a repair operation on its media. +For example, a CXL device with DRAM components that support PPR features +may implement PPR Maintenance operations. Soft PPR (sPPR), is a temporary +row repair. Soft PPR may be faster, but the repair is lost with a power +cycle. + +Sysfs files for memory repair are documented in +`Documentation/ABI/testing/sysfs-edac-memory-repair` diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig index 1c67bf844993..540d06011d80 100644 --- a/drivers/cxl/Kconfig +++ b/drivers/cxl/Kconfig @@ -121,6 +121,7 @@ config CXL_EDAC_MEM_FEATURES depends on EDAC >= CXL_BUS depends on EDAC_SCRUB depends on EDAC_ECS + depends on EDAC_MEM_REPAIR help The CXL EDAC memory feature control is optional and allows host to control the EDAC memory features configurations of CXL memory diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c index 1110685ed41a..f0fb7bac6544 100644 --- a/drivers/cxl/core/edac.c +++ b/drivers/cxl/core/edac.c @@ -14,12 +14,13 @@ #include #include #include +#include #include #include #include #include "core.h" -#define CXL_NR_EDAC_DEV_FEATURES 2 +#define CXL_NR_EDAC_DEV_FEATURES 3 static struct rw_semaphore *cxl_acquire(struct rw_semaphore *rwsem) { @@ -31,6 +32,16 @@ static struct rw_semaphore *cxl_acquire(struct rw_semaphore *rwsem) DEFINE_FREE(cxl_unlock, struct rw_semaphore *, if (_T) up_read(_T)) +static bool cxl_is_memdev_memory_online(const struct cxl_memdev *cxlmd) +{ + struct cxl_port *port = cxlmd->endpoint; + + if (port && is_cxl_endpoint(port) && cxl_num_decoders_committed(port)) + return true; + + return false; +} + /* * CXL memory patrol scrub control */ @@ -772,10 +783,348 @@ static int cxl_memdev_ecs_init(struct cxl_memdev *cxlmd, return 0; } +/* + * CXL memory soft PPR & hard PPR control definitions + */ +struct cxl_ppr_context { + uuid_t repair_uuid; + u8 instance; + u16 get_feat_size; + u16 set_feat_size; + u8 get_version; + u8 set_version; + u16 effects; + struct cxl_memdev *cxlmd; + enum edac_mem_repair_type repair_type; + bool persist_mode; + u64 dpa; + u32 nibble_mask; +}; + +/** + * struct cxl_memdev_ppr_params - CXL memory PPR parameter data structure. + * @dpa: device physical address. + * @op_class: PPR operation class. + * @op_subclass: PPR operation subclass. + * @media_accessible: memory media is accessible or not during PPR operation. + * @data_retained: data is retained or not during PPR operation. + */ +struct cxl_memdev_ppr_params { + u64 dpa; + u8 op_class; + u8 op_subclass; + bool media_accessible; + bool data_retained; +}; + +/* + * See CXL rev 3.2 @8.2.10.7.2.1 Table 8-128 sPPR Feature Readable Attributes + * + * See CXL rev 3.2 @8.2.10.7.2.2 Table 8-131 hPPR Feature Readable Attributes + */ +#define CXL_MEMDEV_PPR_QUERY_RESOURCE_FLAG BIT(0) + +#define CXL_MEMDEV_PPR_DEVICE_INITIATED_MASK BIT(0) + +#define CXL_MEMDEV_PPR_FLAG_DPA_SUPPORT_MASK BIT(0) +#define CXL_MEMDEV_PPR_FLAG_NIBBLE_SUPPORT_MASK BIT(1) +#define CXL_MEMDEV_PPR_FLAG_MEM_SPARING_EV_REC_SUPPORT_MASK BIT(2) +#define CXL_MEMDEV_PPR_FLAG_DEV_INITED_PPR_AT_BOOT_CAP_MASK BIT(3) + +#define CXL_MEMDEV_PPR_RESTRICTION_FLAG_MEDIA_ACCESSIBLE_MASK BIT(0) +#define CXL_MEMDEV_PPR_RESTRICTION_FLAG_DATA_RETAINED_MASK BIT(2) + +#define CXL_MEMDEV_PPR_SPARING_EV_REC_EN_MASK BIT(0) +#define CXL_MEMDEV_PPR_DEV_INITED_PPR_AT_BOOT_EN_MASK BIT(1) + +struct cxl_memdev_repair_rd_attrs_hdr { + u8 max_op_latency; + __le16 op_cap; + __le16 op_mode; + u8 op_class; + u8 op_subclass; + u8 rsvd[9]; +} __packed; + +struct cxl_memdev_ppr_rd_attrs { + struct cxl_memdev_repair_rd_attrs_hdr hdr; + u8 ppr_flags; + __le16 restriction_flags; + u8 ppr_op_mode; +} __packed; + +/* + * See CXL rev 3.2 @8.2.10.7.1.2 Table 8-118 sPPR Maintenance Input Payload + * + * See CXL rev 3.2 @8.2.10.7.1.3 Table 8-119 hPPR Maintenance Input Payload + */ +struct cxl_memdev_ppr_maintenance_attrs { + u8 flags; + __le64 dpa; + u8 nibble_mask[3]; +} __packed; + +static int cxl_mem_ppr_get_attrs(struct cxl_ppr_context *cxl_ppr_ctx, + struct cxl_memdev_ppr_params *params) +{ + size_t rd_data_size = sizeof(struct cxl_memdev_ppr_rd_attrs); + struct cxl_memdev *cxlmd = cxl_ppr_ctx->cxlmd; + struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox; + u16 restriction_flags; + size_t data_size; + u16 return_code; + + struct cxl_memdev_ppr_rd_attrs *rd_attrs __free(kfree) = + kmalloc(rd_data_size, GFP_KERNEL); + if (!rd_attrs) + return -ENOMEM; + + data_size = cxl_get_feature(cxl_mbox, &cxl_ppr_ctx->repair_uuid, + CXL_GET_FEAT_SEL_CURRENT_VALUE, rd_attrs, + rd_data_size, 0, &return_code); + if (!data_size) + return -EIO; + + params->op_class = rd_attrs->hdr.op_class; + params->op_subclass = rd_attrs->hdr.op_subclass; + restriction_flags = le16_to_cpu(rd_attrs->restriction_flags); + params->media_accessible = + FIELD_GET(CXL_MEMDEV_PPR_RESTRICTION_FLAG_MEDIA_ACCESSIBLE_MASK, + restriction_flags) ^ 1; + params->data_retained = + FIELD_GET(CXL_MEMDEV_PPR_RESTRICTION_FLAG_DATA_RETAINED_MASK, + restriction_flags) ^ 1; + + return 0; +} + +static int cxl_mem_do_ppr_op(struct device *dev, + struct cxl_ppr_context *cxl_ppr_ctx, + struct cxl_memdev_ppr_params *rd_params) +{ + struct cxl_memdev_ppr_maintenance_attrs maintenance_attrs; + struct cxl_memdev *cxlmd = cxl_ppr_ctx->cxlmd; + struct cxl_mem_repair_attrbs attrbs = { 0 }; + + if (!rd_params->media_accessible || !rd_params->data_retained) { + /* Memory to repair must be offline */ + if (cxl_is_memdev_memory_online(cxlmd)) + return -EBUSY; + } else { + if (cxl_is_memdev_memory_online(cxlmd)) { + /* Check memory to repair is from the current boot */ + attrbs.repair_type = CXL_PPR; + attrbs.dpa = cxl_ppr_ctx->dpa; + attrbs.nibble_mask = cxl_ppr_ctx->nibble_mask; + if (!cxl_find_rec_dram(cxlmd, &attrbs) && + !cxl_find_rec_gen_media(cxlmd, &attrbs)) + return -EINVAL; + } + } + + memset(&maintenance_attrs, 0, sizeof(maintenance_attrs)); + maintenance_attrs.flags = 0; + maintenance_attrs.dpa = cpu_to_le64(cxl_ppr_ctx->dpa); + put_unaligned_le24(cxl_ppr_ctx->nibble_mask, + maintenance_attrs.nibble_mask); + + return cxl_do_maintenance(&cxlmd->cxlds->cxl_mbox, rd_params->op_class, + rd_params->op_subclass, &maintenance_attrs, + sizeof(maintenance_attrs)); +} + +static int cxl_mem_ppr_set_attrs(struct device *dev, + struct cxl_ppr_context *cxl_ppr_ctx) +{ + struct cxl_memdev_ppr_params rd_params; + int ret; + + ret = cxl_mem_ppr_get_attrs(cxl_ppr_ctx, &rd_params); + if (ret) + return ret; + + struct rw_semaphore *region_lock __free(cxl_unlock) = + cxl_acquire(&cxl_region_rwsem); + if (!region_lock) + return -EINTR; + + struct rw_semaphore *dpa_lock __free(cxl_unlock) = + cxl_acquire(&cxl_dpa_rwsem); + if (!dpa_lock) + return -EINTR; + + ret = cxl_mem_do_ppr_op(dev, cxl_ppr_ctx, &rd_params); + + return ret; +} + +static int cxl_ppr_get_repair_type(struct device *dev, void *drv_data, + const char **repair_type) +{ + *repair_type = edac_repair_type[EDAC_PPR]; + + return 0; +} + +static int cxl_ppr_get_persist_mode(struct device *dev, void *drv_data, + bool *persist_mode) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + + *persist_mode = cxl_ppr_ctx->persist_mode; + + return 0; +} + +static int cxl_get_ppr_safe_when_in_use(struct device *dev, void *drv_data, + bool *safe) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + struct cxl_memdev_ppr_params params; + int ret; + + ret = cxl_mem_ppr_get_attrs(cxl_ppr_ctx, ¶ms); + if (ret) + return ret; + + *safe = params.media_accessible & params.data_retained; + + return 0; +} + +static int cxl_ppr_get_min_dpa(struct device *dev, void *drv_data, u64 *min_dpa) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + struct cxl_memdev *cxlmd = cxl_ppr_ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + *min_dpa = cxlds->dpa_res.start; + + return 0; +} + +static int cxl_ppr_get_max_dpa(struct device *dev, void *drv_data, u64 *max_dpa) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + struct cxl_memdev *cxlmd = cxl_ppr_ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + *max_dpa = cxlds->dpa_res.end; + + return 0; +} + +static int cxl_ppr_get_dpa(struct device *dev, void *drv_data, u64 *dpa) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + + *dpa = cxl_ppr_ctx->dpa; + + return 0; +} + +static int cxl_ppr_set_dpa(struct device *dev, void *drv_data, u64 dpa) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + struct cxl_memdev *cxlmd = cxl_ppr_ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + if (dpa < cxlds->dpa_res.start || dpa > cxlds->dpa_res.end) + return -EINVAL; + + cxl_ppr_ctx->dpa = dpa; + + return 0; +} + +static int cxl_ppr_get_nibble_mask(struct device *dev, void *drv_data, + u32 *nibble_mask) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + + *nibble_mask = cxl_ppr_ctx->nibble_mask; + + return 0; +} + +static int cxl_ppr_set_nibble_mask(struct device *dev, void *drv_data, + u32 nibble_mask) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + + cxl_ppr_ctx->nibble_mask = nibble_mask; + + return 0; +} + +static int cxl_do_ppr(struct device *dev, void *drv_data, u32 val) +{ + struct cxl_ppr_context *cxl_ppr_ctx = drv_data; + + if (!cxl_ppr_ctx->dpa || val != EDAC_DO_MEM_REPAIR) + return -EINVAL; + + return cxl_mem_ppr_set_attrs(dev, cxl_ppr_ctx); +} + +static const struct edac_mem_repair_ops cxl_sppr_ops = { + .get_repair_type = cxl_ppr_get_repair_type, + .get_persist_mode = cxl_ppr_get_persist_mode, + .get_repair_safe_when_in_use = cxl_get_ppr_safe_when_in_use, + .get_min_dpa = cxl_ppr_get_min_dpa, + .get_max_dpa = cxl_ppr_get_max_dpa, + .get_dpa = cxl_ppr_get_dpa, + .set_dpa = cxl_ppr_set_dpa, + .get_nibble_mask = cxl_ppr_get_nibble_mask, + .set_nibble_mask = cxl_ppr_set_nibble_mask, + .do_repair = cxl_do_ppr, +}; + +static int cxl_memdev_soft_ppr_init(struct cxl_memdev *cxlmd, + struct edac_dev_feature *ras_feature, + u8 repair_inst) +{ + struct cxl_ppr_context *cxl_sppr_ctx; + struct cxl_feat_entry *feat_entry; + + feat_entry = cxl_get_feature_entry(cxlmd->cxlds, &CXL_FEAT_SPPR_UUID); + if (!feat_entry) + return -EOPNOTSUPP; + + if (!(le32_to_cpu(feat_entry->flags) & CXL_FEATURE_F_CHANGEABLE)) + return -EOPNOTSUPP; + + cxl_sppr_ctx = + devm_kzalloc(&cxlmd->dev, sizeof(*cxl_sppr_ctx), GFP_KERNEL); + if (!cxl_sppr_ctx) + return -ENOMEM; + + *cxl_sppr_ctx = (struct cxl_ppr_context){ + .get_feat_size = le16_to_cpu(feat_entry->get_feat_size), + .set_feat_size = le16_to_cpu(feat_entry->set_feat_size), + .get_version = feat_entry->get_feat_ver, + .set_version = feat_entry->set_feat_ver, + .effects = le16_to_cpu(feat_entry->effects), + .cxlmd = cxlmd, + .repair_type = EDAC_PPR, + .persist_mode = 0, + .instance = repair_inst, + }; + uuid_copy(&cxl_sppr_ctx->repair_uuid, &CXL_FEAT_SPPR_UUID); + + ras_feature->ft_type = RAS_FEAT_MEM_REPAIR; + ras_feature->instance = cxl_sppr_ctx->instance; + ras_feature->mem_repair_ops = &cxl_sppr_ops; + ras_feature->ctx = cxl_sppr_ctx; + + return 0; +} + int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) { struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; int num_ras_features = 0; + u8 repair_inst = 0; u8 scrub_inst = 0; int rc; @@ -794,6 +1143,16 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) if (rc != -EOPNOTSUPP) num_ras_features++; + rc = cxl_memdev_soft_ppr_init(cxlmd, &ras_features[num_ras_features], + repair_inst); + if (rc < 0 && rc != -EOPNOTSUPP) + return rc; + + if (rc != -EOPNOTSUPP) { + repair_inst++; + num_ras_features++; + } + char *cxl_dev_name __free(kfree) = kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlmd->dev)); diff --git a/drivers/edac/mem_repair.c b/drivers/edac/mem_repair.c index 3b1a845457b0..bf7e01a8b4dd 100755 --- a/drivers/edac/mem_repair.c +++ b/drivers/edac/mem_repair.c @@ -45,6 +45,11 @@ struct edac_mem_repair_context { struct attribute_group group; }; +const char * const edac_repair_type[] = { + [EDAC_PPR] = "ppr", +}; +EXPORT_SYMBOL_GPL(edac_repair_type); + #define TO_MR_DEV_ATTR(_dev_attr) \ container_of(_dev_attr, struct edac_mem_repair_dev_attr, dev_attr) diff --git a/include/linux/edac.h b/include/linux/edac.h index 451f9c152c99..5669d8d2509a 100644 --- a/include/linux/edac.h +++ b/include/linux/edac.h @@ -745,9 +745,12 @@ static inline int edac_ecs_get_desc(struct device *ecs_dev, #endif /* CONFIG_EDAC_ECS */ enum edac_mem_repair_type { + EDAC_PPR, EDAC_REPAIR_MAX }; +extern const char * const edac_repair_type[]; + enum edac_mem_repair_cmd { EDAC_DO_MEM_REPAIR = 1, }; From patchwork Thu Mar 20 18:04:45 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shiju Jose X-Patchwork-Id: 14024305 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 047F622A814; Thu, 20 Mar 2025 18:05:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.176.79.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493955; cv=none; b=mRGWDFFdbzpjIHna5YWucF2qZ3+VNzJ5OxZ3zONfmJZh6KqjDHvOyFaaCNb6lxGh1/7NbedSV2UkgirAdpmxTee8fK5+qpzeljRjIaQAaOtpsuCle5S19jRV4sDRhv4/H6cEaid9Mg+/tJIFa7NMIEv58gsH5u0c6ZqUTzJF8y0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742493955; c=relaxed/simple; bh=3jhvucr2ds8qvfHiTFuqrBHJ+GGQmvxZQ7lD7O2VwW0=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=j+wmh7T56+sS4wGw7FpRvNpbCib36PFjVgdjCn/1kBRzIpvnaPMkrOvM3leFDCo5VKLoz8CGqE9Yo9PclTrek//DUAlMLi8FD6DJb1YiOhq0TMwjK265vsKgh9mRiQnWI8QyGiL/RtfI7pZOQe7rzQzjIL+vxMrQr2TkOERn8Mc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; arc=none smtp.client-ip=185.176.79.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Received: from mail.maildlp.com (unknown [172.18.186.216]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4ZJYMB66Jtz67HSr; Fri, 21 Mar 2025 02:02:30 +0800 (CST) Received: from frapeml500007.china.huawei.com (unknown [7.182.85.172]) by mail.maildlp.com (Postfix) with ESMTPS id 178711405A0; Fri, 21 Mar 2025 02:05:51 +0800 (CST) Received: from P_UKIT01-A7bmah.china.huawei.com (10.48.156.145) by frapeml500007.china.huawei.com (7.182.85.172) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Thu, 20 Mar 2025 19:05:49 +0100 From: To: , , , , , , , , , CC: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , Subject: [PATCH v2 8/8] cxl/memfeature: Add CXL memory device memory sparing control feature Date: Thu, 20 Mar 2025 18:04:45 +0000 Message-ID: <20250320180450.539-9-shiju.jose@huawei.com> X-Mailer: git-send-email 2.43.0.windows.1 In-Reply-To: <20250320180450.539-1-shiju.jose@huawei.com> References: <20250320180450.539-1-shiju.jose@huawei.com> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: lhrpeml100003.china.huawei.com (7.191.160.210) To frapeml500007.china.huawei.com (7.182.85.172) From: Shiju Jose Memory sparing is defined as a repair function that replaces a portion of memory with a portion of functional memory at that same DPA. The subclasses for this operation vary in terms of the scope of the sparing being performed. The cacheline sparing subclass refers to a sparing action that can replace a full cacheline. Row sparing is provided as an alternative to PPR sparing functions and its scope is that of a single DDR row. As per CXL r3.2 Table 8-125 foot note 1. Memory sparing is preferred over PPR when possible. Bank sparing allows an entire bank to be replaced. Rank sparing is defined as an operation in which an entire DDR rank is replaced. Memory sparing maintenance operations may be supported by CXL devices that implement CXL.mem protocol. A sparing maintenance operation requests the CXL device to perform a repair operation on its media. For example, a CXL device with DRAM components that support memory sparing features may implement sparing maintenance operations. The host may issue a query command by setting query resources flag in the input payload (CXL spec 3.2 Table 8-120) to determine availability of sparing resources for a given address. In response to a query request, the device shall report the resource availability by producing the memory sparing event record (CXL spec 3.2 Table 8-60) in which the Channel, Rank, Nibble Mask, Bank Group, Bank, Row, Column, Sub-Channel fields are a copy of the values specified in the request. During the execution of a sparing maintenance operation, a CXL memory device: - may not retain data - may not be able to process CXL.mem requests correctly. These CXL memory device capabilities are specified by restriction flags in the memory sparing feature readable attributes. When a CXL device identifies error on a memory component, the device may inform the host about the need for a memory sparing maintenance operation by using DRAM event record, where the 'maintenance needed' flag may set. The event record contains some of the DPA, Channel, Rank, Nibble Mask, Bank Group, Bank, Row, Column, Sub-Channel fields that should be repaired. The userspace tool requests for maintenance operation if the 'maintenance needed' flag set in the CXL DRAM error record. CXL spec 3.2 section 8.2.10.7.1.4 describes the device's memory sparing maintenance operation feature. CXL spec 3.2 section 8.2.10.7.2.3 describes the memory sparing feature discovery and configuration. Add support for controlling CXL memory device memory sparing feature. Register with EDAC driver, which gets the memory repair attr descriptors from the EDAC memory repair driver and exposes sysfs repair control attributes for memory sparing to the userspace. For example CXL memory sparing control for the CXL mem0 device is exposed in /sys/bus/edac/devices/cxl_mem0/mem_repairX/ Use case ======== 1. CXL device identifies a failure in a memory component, report to userspace in a CXL DRAM trace event with DPA and other attributes of memory to repair such as channel, rank, nibble mask, bank Group, bank, row, column, sub-channel. 2. Rasdaemon process the trace event and may issue query request in sysfs check resources available for memory sparing if either of the following conditions met. - 'maintenance needed' flag set in the event record. - 'threshold event' flag set for CVME threshold feature. - If the previous case is not enough, may be when the number of corrected error reported on a CXL.mem media to the user space exceeds an error threshold set in the userspace policy. 3. Rasdaemon process the memory sparing trace event and issue repair request for memory sparing. Kernel CXL driver shall report memory sparing event record to the userspace with the resource availability in order rasdaemon to process the event record and issue a repair request in sysfs for the memory sparing operation in the CXL device. Note: Based on the feedbacks from the community 'query' sysfs attribute is removed and reporting memory sparing error record to the userspace are not supported. Instead userspace issues sparing operation and kernel does the same to the CXL memory device, when 'maintenance needed' flag set in the DRAM event record. Add checks to ensure the memory to be repaired is offline and if online, then originates from a CXL DRAM error record reported in the current boot before requesting a memory sparing operation on the device. Tested for memory sparing control feature with "hw/cxl: Add memory sparing control feature" Repository: "https://gitlab.com/shiju.jose/qemu.git" Branch: cxl-ras-features-2024-10-24 Reviewed-by: Jonathan Cameron Signed-off-by: Shiju Jose --- Documentation/edac/memory_repair.rst | 13 + drivers/cxl/core/edac.c | 550 ++++++++++++++++++++++++++- drivers/edac/mem_repair.c | 4 + include/linux/edac.h | 4 + 4 files changed, 569 insertions(+), 2 deletions(-) diff --git a/Documentation/edac/memory_repair.rst b/Documentation/edac/memory_repair.rst index cb0d91ccebfd..3725a7c76697 100644 --- a/Documentation/edac/memory_repair.rst +++ b/Documentation/edac/memory_repair.rst @@ -135,5 +135,18 @@ may implement PPR Maintenance operations. Soft PPR (sPPR), is a temporary row repair. Soft PPR may be faster, but the repair is lost with a power cycle. +2. CXL memory sparing + +Memory sparing is defined as a repair function that replaces a portion of +memory with a portion of functional memory at that same DPA. The subclass +for this operation, cacheline/row/bank/rank sparing, vary in terms of the +scope of the sparing being performed. + +Memory sparing maintenance operations may be supported by CXL devices that +implement CXL.mem protocol. A sparing maintenance operation requests the +CXL device to perform a repair operation on its media. For example, a CXL +device with DRAM components that support memory sparing features may +implement sparing maintenance operations. + Sysfs files for memory repair are documented in `Documentation/ABI/testing/sysfs-edac-memory-repair` diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c index f0fb7bac6544..0721a9db79f1 100644 --- a/drivers/cxl/core/edac.c +++ b/drivers/cxl/core/edac.c @@ -19,8 +19,9 @@ #include #include #include "core.h" +#include "trace.h" -#define CXL_NR_EDAC_DEV_FEATURES 3 +#define CXL_NR_EDAC_DEV_FEATURES 7 static struct rw_semaphore *cxl_acquire(struct rw_semaphore *rwsem) { @@ -1120,13 +1121,545 @@ static int cxl_memdev_soft_ppr_init(struct cxl_memdev *cxlmd, return 0; } +/* + * CXL memory sparing control + */ +enum cxl_mem_sparing_granularity { + CXL_MEM_SPARING_CACHELINE, + CXL_MEM_SPARING_ROW, + CXL_MEM_SPARING_BANK, + CXL_MEM_SPARING_RANK, + CXL_MEM_SPARING_MAX +}; + +struct cxl_mem_sparing_context { + struct cxl_memdev *cxlmd; + uuid_t repair_uuid; + u16 get_feat_size; + u16 set_feat_size; + u16 effects; + u8 instance; + u8 get_version; + u8 set_version; + u8 channel; + u8 rank; + u8 bank_group; + u32 nibble_mask; + u64 dpa; + u32 row; + u16 column; + u8 bank; + u8 sub_channel; + enum edac_mem_repair_type repair_type; + bool persist_mode; + enum cxl_mem_sparing_granularity granularity; +}; + +struct cxl_memdev_sparing_params { + u8 op_class; + u8 op_subclass; + bool cap_safe_when_in_use; + bool cap_hard_sparing; + bool cap_soft_sparing; +}; + +#define CXL_MEMDEV_SPARING_RD_CAP_SAFE_IN_USE_MASK BIT(0) +#define CXL_MEMDEV_SPARING_RD_CAP_HARD_SPARING_MASK BIT(1) +#define CXL_MEMDEV_SPARING_RD_CAP_SOFT_SPARING_MASK BIT(2) + +#define CXL_MEMDEV_SPARING_WR_DEVICE_INITIATED_MASK BIT(0) + +#define CXL_MEMDEV_SPARING_QUERY_RESOURCE_FLAG BIT(0) +#define CXL_MEMDEV_SET_HARD_SPARING_FLAG BIT(1) +#define CXL_MEMDEV_SPARING_SUB_CHANNEL_VALID_FLAG BIT(2) +#define CXL_MEMDEV_SPARING_NIB_MASK_VALID_FLAG BIT(3) + +/* + * See CXL spec rev 3.2 @8.2.10.7.2.3 Table 8-134 Memory Sparing Feature + * Readable Attributes. + */ +struct cxl_memdev_sparing_rd_attrs { + struct cxl_memdev_repair_rd_attrs_hdr hdr; + u8 rsvd; + __le16 restriction_flags; +} __packed; + +/* + * See CXL spec rev 3.2 @8.2.10.7.1.4 Table 8-120 Memory Sparing Input Payload. + */ +struct cxl_memdev_sparing_in_payload { + u8 flags; + u8 channel; + u8 rank; + u8 nibble_mask[3]; + u8 bank_group; + u8 bank; + u8 row[3]; + __le16 column; + u8 sub_channel; +} __packed; + +static int +cxl_mem_sparing_get_attrs(struct cxl_mem_sparing_context *cxl_sparing_ctx, + struct cxl_memdev_sparing_params *params) +{ + size_t rd_data_size = sizeof(struct cxl_memdev_sparing_rd_attrs); + struct cxl_memdev *cxlmd = cxl_sparing_ctx->cxlmd; + struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox; + u16 restriction_flags; + size_t data_size; + u16 return_code; + struct cxl_memdev_sparing_rd_attrs *rd_attrs __free(kfree) = + kzalloc(rd_data_size, GFP_KERNEL); + if (!rd_attrs) + return -ENOMEM; + + data_size = cxl_get_feature(cxl_mbox, &cxl_sparing_ctx->repair_uuid, + CXL_GET_FEAT_SEL_CURRENT_VALUE, rd_attrs, + rd_data_size, 0, &return_code); + if (!data_size) + return -EIO; + + params->op_class = rd_attrs->hdr.op_class; + params->op_subclass = rd_attrs->hdr.op_subclass; + restriction_flags = le16_to_cpu(rd_attrs->restriction_flags); + params->cap_safe_when_in_use = + FIELD_GET(CXL_MEMDEV_SPARING_RD_CAP_SAFE_IN_USE_MASK, + restriction_flags) ^ 1; + params->cap_hard_sparing = + FIELD_GET(CXL_MEMDEV_SPARING_RD_CAP_HARD_SPARING_MASK, + restriction_flags); + params->cap_soft_sparing = + FIELD_GET(CXL_MEMDEV_SPARING_RD_CAP_SOFT_SPARING_MASK, + restriction_flags); + + return 0; +} + +static struct cxl_event_dram * +cxl_mem_get_rec_dram(struct cxl_memdev *cxlmd, + struct cxl_mem_sparing_context *ctx) +{ + struct cxl_mem_repair_attrbs attrbs = { 0 }; + + attrbs.dpa = ctx->dpa; + attrbs.channel = ctx->channel; + attrbs.rank = ctx->rank; + attrbs.nibble_mask = ctx->nibble_mask; + switch (ctx->repair_type) { + case EDAC_CACHELINE_SPARING: + attrbs.repair_type = CXL_CACHELINE_SPARING; + attrbs.bank_group = ctx->bank_group; + attrbs.bank = ctx->bank; + attrbs.row = ctx->row; + attrbs.column = ctx->column; + attrbs.sub_channel = ctx->sub_channel; + break; + case EDAC_ROW_SPARING: + attrbs.repair_type = CXL_ROW_SPARING; + attrbs.bank_group = ctx->bank_group; + attrbs.bank = ctx->bank; + attrbs.row = ctx->row; + break; + case EDAC_BANK_SPARING: + attrbs.repair_type = CXL_BANK_SPARING; + attrbs.bank_group = ctx->bank_group; + attrbs.bank = ctx->bank; + break; + case EDAC_RANK_SPARING: + attrbs.repair_type = CXL_BANK_SPARING; + break; + default: + return NULL; + } + + return cxl_find_rec_dram(cxlmd, &attrbs); +} + +static int +cxl_mem_do_sparing_op(struct device *dev, + struct cxl_mem_sparing_context *cxl_sparing_ctx, + struct cxl_memdev_sparing_params *rd_params) +{ + struct cxl_memdev *cxlmd = cxl_sparing_ctx->cxlmd; + struct cxl_memdev_sparing_in_payload sparing_pi; + struct cxl_event_dram *rec = NULL; + u16 validity_flags = 0; + + if (!rd_params->cap_safe_when_in_use) { + /* Memory to repair must be offline */ + if (cxl_is_memdev_memory_online(cxlmd)) + return -EBUSY; + } else { + if (cxl_is_memdev_memory_online(cxlmd)) { + rec = cxl_mem_get_rec_dram(cxlmd, cxl_sparing_ctx); + if (!rec) + return -EINVAL; + + validity_flags = + get_unaligned_le16(rec->media_hdr.validity_flags); + if (!validity_flags) + return -EINVAL; + } + } + + memset(&sparing_pi, 0, sizeof(sparing_pi)); + sparing_pi.flags = + FIELD_PREP(CXL_MEMDEV_SPARING_QUERY_RESOURCE_FLAG, 0); + if (cxl_sparing_ctx->persist_mode) + sparing_pi.flags |= + FIELD_PREP(CXL_MEMDEV_SET_HARD_SPARING_FLAG, 1); + + switch (cxl_sparing_ctx->repair_type) { + case EDAC_CACHELINE_SPARING: + sparing_pi.column = cpu_to_le16(cxl_sparing_ctx->column); + if (!rec || (validity_flags & CXL_DER_VALID_SUB_CHANNEL)) { + sparing_pi.flags |= + FIELD_PREP(CXL_MEMDEV_SPARING_SUB_CHANNEL_VALID_FLAG, 1); + sparing_pi.sub_channel = cxl_sparing_ctx->sub_channel; + } + fallthrough; + case EDAC_ROW_SPARING: + put_unaligned_le24(cxl_sparing_ctx->row, sparing_pi.row); + fallthrough; + case EDAC_BANK_SPARING: + sparing_pi.bank_group = cxl_sparing_ctx->bank_group; + sparing_pi.bank = cxl_sparing_ctx->bank; + fallthrough; + case EDAC_RANK_SPARING: + sparing_pi.rank = cxl_sparing_ctx->rank; + fallthrough; + default: + sparing_pi.channel = cxl_sparing_ctx->channel; + if ((rec && (validity_flags & CXL_DER_VALID_NIBBLE)) || + (!rec && (!cxl_sparing_ctx->nibble_mask || + (cxl_sparing_ctx->nibble_mask & 0xFFFFFF)))) { + sparing_pi.flags |= + FIELD_PREP(CXL_MEMDEV_SPARING_NIB_MASK_VALID_FLAG, 1); + put_unaligned_le24(cxl_sparing_ctx->nibble_mask, + sparing_pi.nibble_mask); + } + break; + } + + return cxl_do_maintenance(&cxlmd->cxlds->cxl_mbox, rd_params->op_class, + rd_params->op_subclass, &sparing_pi, + sizeof(sparing_pi)); +} + +static int cxl_mem_sparing_set_attrs(struct device *dev, + struct cxl_mem_sparing_context *ctx) +{ + struct cxl_memdev_sparing_params rd_params; + int ret; + + ret = cxl_mem_sparing_get_attrs(ctx, &rd_params); + if (ret) + return ret; + + struct rw_semaphore *region_lock __free(cxl_unlock) = + cxl_acquire(&cxl_region_rwsem); + if (!region_lock) + return -EINTR; + + struct rw_semaphore *dpa_lock __free(cxl_unlock) = + cxl_acquire(&cxl_dpa_rwsem); + if (!dpa_lock) + return -EINTR; + + ret = cxl_mem_do_sparing_op(dev, ctx, &rd_params); + + return ret; +} + +static int cxl_mem_sparing_get_repair_type(struct device *dev, void *drv_data, + const char **repair_type) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + + switch (ctx->repair_type) { + case EDAC_CACHELINE_SPARING: + case EDAC_ROW_SPARING: + case EDAC_BANK_SPARING: + case EDAC_RANK_SPARING: + *repair_type = edac_repair_type[ctx->repair_type]; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define CXL_SPARING_GET_ATTR(attrib, data_type) \ + static int cxl_mem_sparing_get_##attrib( \ + struct device *dev, void *drv_data, data_type *val) \ + { \ + struct cxl_mem_sparing_context *ctx = drv_data; \ + \ + *val = ctx->attrib; \ + \ + return 0; \ + } +CXL_SPARING_GET_ATTR(persist_mode, bool) +CXL_SPARING_GET_ATTR(dpa, u64) +CXL_SPARING_GET_ATTR(nibble_mask, u32) +CXL_SPARING_GET_ATTR(bank_group, u32) +CXL_SPARING_GET_ATTR(bank, u32) +CXL_SPARING_GET_ATTR(rank, u32) +CXL_SPARING_GET_ATTR(row, u32) +CXL_SPARING_GET_ATTR(column, u32) +CXL_SPARING_GET_ATTR(channel, u32) +CXL_SPARING_GET_ATTR(sub_channel, u32) + +#define CXL_SPARING_SET_ATTR(attrib, data_type) \ + static int cxl_mem_sparing_set_##attrib(struct device *dev, \ + void *drv_data, data_type val) \ + { \ + struct cxl_mem_sparing_context *ctx = drv_data; \ + \ + ctx->attrib = val; \ + \ + return 0; \ + } +CXL_SPARING_SET_ATTR(nibble_mask, u32) +CXL_SPARING_SET_ATTR(bank_group, u32) +CXL_SPARING_SET_ATTR(bank, u32) +CXL_SPARING_SET_ATTR(rank, u32) +CXL_SPARING_SET_ATTR(row, u32) +CXL_SPARING_SET_ATTR(column, u32) +CXL_SPARING_SET_ATTR(channel, u32) +CXL_SPARING_SET_ATTR(sub_channel, u32) + +static int cxl_mem_sparing_set_persist_mode(struct device *dev, void *drv_data, + bool persist_mode) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + struct cxl_memdev_sparing_params params; + int ret; + + ret = cxl_mem_sparing_get_attrs(ctx, ¶ms); + if (ret) + return ret; + + if ((persist_mode && params.cap_hard_sparing) || + (!persist_mode && params.cap_soft_sparing)) + ctx->persist_mode = persist_mode; + else + return -EOPNOTSUPP; + + return 0; +} + +static int cxl_get_mem_sparing_safe_when_in_use(struct device *dev, + void *drv_data, bool *safe) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + struct cxl_memdev_sparing_params params; + int ret; + + ret = cxl_mem_sparing_get_attrs(ctx, ¶ms); + if (ret) + return ret; + + *safe = params.cap_safe_when_in_use; + + return 0; +} + +static int cxl_mem_sparing_get_min_dpa(struct device *dev, void *drv_data, + u64 *min_dpa) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + struct cxl_memdev *cxlmd = ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + *min_dpa = cxlds->dpa_res.start; + + return 0; +} + +static int cxl_mem_sparing_get_max_dpa(struct device *dev, void *drv_data, + u64 *max_dpa) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + struct cxl_memdev *cxlmd = ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + *max_dpa = cxlds->dpa_res.end; + + return 0; +} + +static int cxl_mem_sparing_set_dpa(struct device *dev, void *drv_data, u64 dpa) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + struct cxl_memdev *cxlmd = ctx->cxlmd; + struct cxl_dev_state *cxlds = cxlmd->cxlds; + + if (dpa < cxlds->dpa_res.start || dpa > cxlds->dpa_res.end) + return -EINVAL; + + ctx->dpa = dpa; + + return 0; +} + +static int cxl_do_mem_sparing(struct device *dev, void *drv_data, u32 val) +{ + struct cxl_mem_sparing_context *ctx = drv_data; + + if (val != EDAC_DO_MEM_REPAIR) + return -EINVAL; + + return cxl_mem_sparing_set_attrs(dev, ctx); +} + +#define RANK_OPS \ + .get_repair_type = cxl_mem_sparing_get_repair_type, \ + .get_persist_mode = cxl_mem_sparing_get_persist_mode, \ + .set_persist_mode = cxl_mem_sparing_set_persist_mode, \ + .get_repair_safe_when_in_use = cxl_get_mem_sparing_safe_when_in_use, \ + .get_min_dpa = cxl_mem_sparing_get_min_dpa, \ + .get_max_dpa = cxl_mem_sparing_get_max_dpa, \ + .get_dpa = cxl_mem_sparing_get_dpa, \ + .set_dpa = cxl_mem_sparing_set_dpa, \ + .get_nibble_mask = cxl_mem_sparing_get_nibble_mask, \ + .set_nibble_mask = cxl_mem_sparing_set_nibble_mask, \ + .get_rank = cxl_mem_sparing_get_rank, \ + .set_rank = cxl_mem_sparing_set_rank, \ + .get_channel = cxl_mem_sparing_get_channel, \ + .set_channel = cxl_mem_sparing_set_channel, \ + .do_repair = cxl_do_mem_sparing + +#define BANK_OPS \ + RANK_OPS, .get_bank_group = cxl_mem_sparing_get_bank_group, \ + .set_bank_group = cxl_mem_sparing_set_bank_group, \ + .get_bank = cxl_mem_sparing_get_bank, \ + .set_bank = cxl_mem_sparing_set_bank + +#define ROW_OPS \ + BANK_OPS, .get_row = cxl_mem_sparing_get_row, \ + .set_row = cxl_mem_sparing_set_row + +#define CACHELINE_OPS \ + ROW_OPS, .get_column = cxl_mem_sparing_get_column, \ + .set_column = cxl_mem_sparing_set_column, \ + .get_sub_channel = cxl_mem_sparing_get_sub_channel, \ + .set_sub_channel = cxl_mem_sparing_set_sub_channel + +static const struct edac_mem_repair_ops cxl_rank_sparing_ops = { + RANK_OPS, +}; + +static const struct edac_mem_repair_ops cxl_bank_sparing_ops = { + BANK_OPS, +}; + +static const struct edac_mem_repair_ops cxl_row_sparing_ops = { + ROW_OPS, +}; + +static const struct edac_mem_repair_ops cxl_cacheline_sparing_ops = { + CACHELINE_OPS, +}; + +struct cxl_mem_sparing_desc { + const uuid_t repair_uuid; + enum edac_mem_repair_type repair_type; + enum cxl_mem_sparing_granularity granularity; + const struct edac_mem_repair_ops *repair_ops; +}; + +static const struct cxl_mem_sparing_desc mem_sparing_desc[] = { + { + .repair_uuid = CXL_FEAT_CACHELINE_SPARING_UUID, + .repair_type = EDAC_CACHELINE_SPARING, + .granularity = CXL_MEM_SPARING_CACHELINE, + .repair_ops = &cxl_cacheline_sparing_ops, + }, + { + .repair_uuid = CXL_FEAT_ROW_SPARING_UUID, + .repair_type = EDAC_ROW_SPARING, + .granularity = CXL_MEM_SPARING_ROW, + .repair_ops = &cxl_row_sparing_ops, + }, + { + .repair_uuid = CXL_FEAT_BANK_SPARING_UUID, + .repair_type = EDAC_BANK_SPARING, + .granularity = CXL_MEM_SPARING_BANK, + .repair_ops = &cxl_bank_sparing_ops, + }, + { + .repair_uuid = CXL_FEAT_RANK_SPARING_UUID, + .repair_type = EDAC_RANK_SPARING, + .granularity = CXL_MEM_SPARING_RANK, + .repair_ops = &cxl_rank_sparing_ops, + }, +}; + +static int cxl_memdev_sparing_init(struct cxl_memdev *cxlmd, + struct edac_dev_feature *ras_feature, + const struct cxl_mem_sparing_desc *desc, + u8 repair_inst) +{ + struct cxl_mem_sparing_context *cxl_sparing_ctx; + struct cxl_memdev_sparing_params rd_params; + struct cxl_feat_entry *feat_entry; + int ret; + + feat_entry = cxl_get_feature_entry(cxlmd->cxlds, &desc->repair_uuid); + if (!feat_entry) + return -EOPNOTSUPP; + + if (!(le32_to_cpu(feat_entry->flags) & CXL_FEATURE_F_CHANGEABLE)) + return -EOPNOTSUPP; + + cxl_sparing_ctx = devm_kzalloc(&cxlmd->dev, sizeof(*cxl_sparing_ctx), + GFP_KERNEL); + if (!cxl_sparing_ctx) + return -ENOMEM; + + *cxl_sparing_ctx = (struct cxl_mem_sparing_context){ + .get_feat_size = le16_to_cpu(feat_entry->get_feat_size), + .set_feat_size = le16_to_cpu(feat_entry->set_feat_size), + .get_version = feat_entry->get_feat_ver, + .set_version = feat_entry->set_feat_ver, + .effects = le16_to_cpu(feat_entry->effects), + .cxlmd = cxlmd, + .repair_type = desc->repair_type, + .granularity = desc->granularity, + .instance = repair_inst++, + }; + uuid_copy(&cxl_sparing_ctx->repair_uuid, &desc->repair_uuid); + + ret = cxl_mem_sparing_get_attrs(cxl_sparing_ctx, &rd_params); + if (ret) + return ret; + + if ((rd_params.cap_soft_sparing && rd_params.cap_hard_sparing) || + rd_params.cap_soft_sparing) + cxl_sparing_ctx->persist_mode = 0; + else if (rd_params.cap_hard_sparing) + cxl_sparing_ctx->persist_mode = 1; + else + return -EOPNOTSUPP; + + ras_feature->ft_type = RAS_FEAT_MEM_REPAIR; + ras_feature->instance = cxl_sparing_ctx->instance; + ras_feature->mem_repair_ops = desc->repair_ops; + ras_feature->ctx = cxl_sparing_ctx; + + return 0; +} + int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) { struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; int num_ras_features = 0; u8 repair_inst = 0; u8 scrub_inst = 0; - int rc; + int i, rc; rc = cxl_memdev_scrub_init(cxlmd, &ras_features[num_ras_features], scrub_inst); @@ -1153,6 +1686,19 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) num_ras_features++; } + for (i = 0; i < CXL_MEM_SPARING_MAX; i++) { + rc = cxl_memdev_sparing_init(cxlmd, + &ras_features[num_ras_features], + &mem_sparing_desc[i], repair_inst); + if (rc == -EOPNOTSUPP) + continue; + if (rc < 0) + return rc; + + repair_inst++; + num_ras_features++; + } + char *cxl_dev_name __free(kfree) = kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlmd->dev)); diff --git a/drivers/edac/mem_repair.c b/drivers/edac/mem_repair.c index bf7e01a8b4dd..c4a2e99c9355 100755 --- a/drivers/edac/mem_repair.c +++ b/drivers/edac/mem_repair.c @@ -47,6 +47,10 @@ struct edac_mem_repair_context { const char * const edac_repair_type[] = { [EDAC_PPR] = "ppr", + [EDAC_CACHELINE_SPARING] = "cacheline-sparing", + [EDAC_ROW_SPARING] = "row-sparing", + [EDAC_BANK_SPARING] = "bank-sparing", + [EDAC_RANK_SPARING] = "rank-sparing", }; EXPORT_SYMBOL_GPL(edac_repair_type); diff --git a/include/linux/edac.h b/include/linux/edac.h index 5669d8d2509a..57c9856a6bd9 100644 --- a/include/linux/edac.h +++ b/include/linux/edac.h @@ -746,6 +746,10 @@ static inline int edac_ecs_get_desc(struct device *ecs_dev, enum edac_mem_repair_type { EDAC_PPR, + EDAC_CACHELINE_SPARING, + EDAC_ROW_SPARING, + EDAC_BANK_SPARING, + EDAC_RANK_SPARING, EDAC_REPAIR_MAX };