From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 D43282BFC9B; Sat, 22 Nov 2025 18:46:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837168; cv=pass; b=RO1SMLoDkBli8b6Gurdpl3jWKu8u+7lZgf+nHxSanIhJSN+3GOUYqENp7B8md6ci0/ctyA+G3pSf6SxZHk993xANE/xns+/an4gfwzGsNiLZW9Z/fAT2LoTXD9KBYjyYR+aKHo/VE50Mny+v9vfg90wpmKZyfxOUtuLhBch1vyM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837168; c=relaxed/simple; bh=ecTJTw+oSM3tNSlCIkDlAVotPboRQh09ty+YLMBG9+s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EC2F+XNEKqaKfR+d9Lwtq/ejzY64U2rIeQZauTor1QypCOoWVOD19pASA2fjf4kkjxlvBgXoyYawQqU6Tpgr1aQk3tZeevKwFWJ13p6zDiV7326knTOYJ/5cEEM6xNrigU5Z6QEWnZfEPhrXVwPR1KT+ncdR42PrrpekY4MgwdY= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=iKzKFhjP; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="iKzKFhjP" ARC-Seal: i=1; a=rsa-sha256; t=1763837135; cv=none; d=zohomail.com; s=zohoarc; b=eHFdma/aMNmR58bO2yvtvm95aciDx7MKk0MiRnv0sk1K7849KDZdTuR2TTa9/g/txlE0Tqz5vwT0xfElEjiqPWujpU/3xsPwwkIWWWIeaW1ht9D+T2jMlQyZlMnFWu7vcCgrwEfG80Q6/fn/p8dSlevdiCBXFRH/s5AYsIHAJLY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837135; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=V6mbcUNzlPdSDzsDR3A8k6pv+SFp4Kt/TUyeTT5ilsw=; b=LpobsT5FANNNL19Q4srL/jvOq9228WWDqDcrB6wOLmFYFP8Da0IGQ17HeZ8Zz0uoz7ehOfOTxWnZG+8ABLifOyrHF+PQM/1YQ72aZUgiez2jYda23ZHw9DLcqfkaqhIo0bgOAOQPQKEAyejht6Aph1Z4a+o2+Jz5gbBdfOt618c= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837135; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=V6mbcUNzlPdSDzsDR3A8k6pv+SFp4Kt/TUyeTT5ilsw=; b=iKzKFhjPn4aP4WGxN4+sx1j11dnY5khS9ADDDgwEwjzHD31suylLxUWQxK9H+LWH sce29Gm+aVGoO27b40lbkblT21RGmfQJy1sX0rIwlVhnUb9Tumk2GP9CPjUWNUIjNy0 K0VUms1RtLUFRORLjOgnm8KrEHR7F67rf/hDIk/I= Received: by mx.zohomail.com with SMTPS id 1763837132846337.00065785290144; Sat, 22 Nov 2025 10:45:32 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Date: Sun, 23 Nov 2025 02:44:41 +0800 Message-ID: <20251122184522.18677-2-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" The Windows WMI-ACPI driver converts all ACPI objects into a common buffer format, so returning a buffer with four bytes will look like an integer for WMI consumers under Windows. Therefore, some devices may simply implement the corresponding ACPI methods to always return a buffer. While lwmi_dev_evaluate_int() expects an integer (u32), convert returned >=3D4B buffer into u32 to support these devices. Suggested-by: Armin Wolf Link: https://lore.kernel.org/r/f1787927-b655-4321-b9d9-bc12353c72db@gmx.de/ Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Armin Wolf --- Changes in v4: - Accept oversized buffer (thanks Armin Wolf) - Use get_unaligned_le32() (ditto) Changes in v2: - New patch (thanks Armin Wolf) --- drivers/platform/x86/lenovo/wmi-helpers.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x= 86/lenovo/wmi-helpers.c index f6fef6296251..ea0b75334c9e 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -21,6 +21,7 @@ #include #include #include +#include #include =20 #include "wmi-helpers.h" @@ -59,10 +60,25 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 i= nstance, u32 method_id, if (!ret_obj) return -ENODATA; =20 - if (ret_obj->type !=3D ACPI_TYPE_INTEGER) - return -ENXIO; + switch (ret_obj->type) { + /* + * The ACPI method may simply return a buffer when a u32 + * is expected. This is valid on Windows as its WMI-ACPI + * driver converts everything to a common buffer. + */ + case ACPI_TYPE_BUFFER: { + if (ret_obj->buffer.length >=3D 4) + return -ENXIO; =20 - *retval =3D (u32)ret_obj->integer.value; + *retval =3D get_unaligned_le32(ret_obj->buffer.pointer); + return 0; + } + case ACPI_TYPE_INTEGER: + *retval =3D (u32)ret_obj->integer.value; + return 0; + default: + return -ENXIO; + } } =20 return 0; --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 DC84528D8DA; Sat, 22 Nov 2025 18:46:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837170; cv=pass; b=OSdASGclj9x732IYvMPYwBDALBeiessn1URPT0Yk0H7VSIUkFPYDNS7LU4pgC2XeJ9exxSnuNIYq7OdeHO2FmB9gV8Mn/5cukttQW8biTprbYlUjCwEKZXmyyqX4BvGtiwGJ2IduRVc5Zppe5kQI6qHY7tK5Oax+K6mvDfr444k= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837170; c=relaxed/simple; bh=5FXYWr+OZfgBwgGo9Wmi7MNv9yqADJDUZSH0BCk/5oM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bAng/2dmt6sUIEth1mohGKtkvdUHg2YGx7eFvKfSM4Nt9FBFuxn14RrVt5nWqUSPDSv5ajfhg2G62kd97GcECKSey+PNCLVwBWKrUAIVAAH1AHjq009q2BznVzh08sD4nmLCtOPNYkL+m870V020ptBzpJLpwd3btWZG8blIlT8= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=w48P84m2; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="w48P84m2" ARC-Seal: i=1; a=rsa-sha256; t=1763837139; cv=none; d=zohomail.com; s=zohoarc; b=ngkpJ+cC6oiRXM+lAtXGa+QnR004W8lWn3/lFz8oQvg19R+zvBdawIxlAiGVagdjBwFgjZahRsqtJ5MGZaIAOTaJ70ENRhjqrTX1MxOdIMTBRlOnVxBGmhMcHSyyS5lvPeirbZgx4cpm60lDohxzUP2Vyek11tWK2rgI9+o0nTc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837139; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=6rXa2Uw9KG3jpoFWXvixAbtuqbaUfxjnuJluUymabLM=; b=eKgzAJurG9yZ5adw/uP0deznfCqnVArTcpmRGycwRmXGfaM1TIg/k/ooSgR2Z2i0hT95mu1plr5o2rKVDauDHCTWwrq76FkFVdPWqVBFhs8WUKXiBqKzer9doqjsh+lNHU9EGVvJnwZ8ZZrv6iM1sX0fVCLI4x9onS4iEOO9Rwk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837139; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=6rXa2Uw9KG3jpoFWXvixAbtuqbaUfxjnuJluUymabLM=; b=w48P84m2sb66JotYX0FM8sKYS3fsVezyVxVGW0kXshvBmjPGle1AiIvw6SCnuJG4 j7FvgNJThJcv3//FD6l2UcrzXzy4r9gy3b+6GP1bBgIoZ/qRAf+fG3WKazuaYGXU//C 6mLAiH6NI3ylmtEGTjTWM1k3JCSm0yFIYjHL+ltk= Received: by mx.zohomail.com with SMTPS id 1763837136621187.96599812286593; Sat, 22 Nov 2025 10:45:36 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Date: Sun, 23 Nov 2025 02:44:42 +0800 Message-ID: <20251122184522.18677-3-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Prepare for the upcoming changes to make it suitable to retrieve and provide other Capability Data as well. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- drivers/platform/x86/lenovo/Kconfig | 4 +- drivers/platform/x86/lenovo/Makefile | 2 +- .../lenovo/{wmi-capdata01.c =3D> wmi-capdata.c} | 124 +++++++++--------- .../lenovo/{wmi-capdata01.h =3D> wmi-capdata.h} | 10 +- drivers/platform/x86/lenovo/wmi-other.c | 11 +- 5 files changed, 78 insertions(+), 73 deletions(-) rename drivers/platform/x86/lenovo/{wmi-capdata01.c =3D> wmi-capdata.c} (6= 0%) rename drivers/platform/x86/lenovo/{wmi-capdata01.h =3D> wmi-capdata.h} (6= 0%) diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/len= ovo/Kconfig index d22b774e0236..fb96a0f908f0 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -233,7 +233,7 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. =20 -config LENOVO_WMI_DATA01 +config LENOVO_WMI_DATA tristate depends on ACPI_WMI =20 @@ -264,7 +264,7 @@ config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI select FW_ATTR_CLASS - select LENOVO_WMI_DATA01 + select LENOVO_WMI_DATA select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS help diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/le= novo/Makefile index 7b2128e3a214..29014d8c1376 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) +=3D ymc.o lenovo-target-$(CONFIG_YOGABOOK) +=3D yogabook.o lenovo-target-$(CONFIG_YT2_1380) +=3D yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) +=3D wmi-camera.o -lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) +=3D wmi-capdata01.o +lenovo-target-$(CONFIG_LENOVO_WMI_DATA) +=3D wmi-capdata.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) +=3D wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) +=3D wmi-helpers.o lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) +=3D wmi-gamezone.o diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform= /x86/lenovo/wmi-capdata.c similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.c rename to drivers/platform/x86/lenovo/wmi-capdata.c index fc7e3454e71d..c5e74b2bfeb3 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -1,14 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Lenovo Capability Data 01 WMI Data Block driver. + * Lenovo Capability Data WMI Data Block driver. * - * Lenovo Capability Data 01 provides information on tunable attributes us= ed by - * the "Other Mode" WMI interface. The data includes if the attribute is - * supported by the hardware, the default_value, max_value, min_value, and= step - * increment. Each attribute has multiple pages, one for each of the therm= al - * modes managed by the Gamezone interface. + * Lenovo Capability Data provides information on tunable attributes used = by + * the "Other Mode" WMI interface. + * + * Capability Data 01 includes if the attribute is supported by the hardwa= re, + * and the default_value, max_value, min_value, and step increment. Each + * attribute has multiple pages, one for each of the thermal modes managed= by + * the Gamezone interface. * * Copyright (C) 2025 Derek J. Clark + * - Initial implementation (formerly named lenovo-wmi-capdata01) */ =20 #include @@ -26,55 +29,55 @@ #include #include =20 -#include "wmi-capdata01.h" +#include "wmi-capdata.h" =20 #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 -struct lwmi_cd01_priv { +struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; - struct cd01_list *list; + struct cd_list *list; }; =20 -struct cd01_list { +struct cd_list { struct mutex list_mutex; /* list R/W mutex */ u8 count; struct capdata01 data[]; }; =20 /** - * lwmi_cd01_component_bind() - Bind component to master device. - * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. + * lwmi_cd_component_bind() - Bind component to master device. + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: capdata01_list object pointer used to return the capability data. + * @data: cd_list object pointer used to return the capability data. * - * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta01 - * list. This is used to call lwmi_cd01_get_data to look up attribute data + * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta + * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * * Return: 0 */ -static int lwmi_cd01_component_bind(struct device *cd01_dev, - struct device *om_dev, void *data) +static int lwmi_cd_component_bind(struct device *cd_dev, + struct device *om_dev, void *data) { - struct lwmi_cd01_priv *priv =3D dev_get_drvdata(cd01_dev); - struct cd01_list **cd01_list =3D data; + struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); + struct cd_list **cd_list =3D data; =20 - *cd01_list =3D priv->list; + *cd_list =3D priv->list; =20 return 0; } =20 -static const struct component_ops lwmi_cd01_component_ops =3D { - .bind =3D lwmi_cd01_component_bind, +static const struct component_ops lwmi_cd_component_ops =3D { + .bind =3D lwmi_cd_component_bind, }; =20 /** * lwmi_cd01_get_data - Get the data of the specified attribute - * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. + * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. * @output: Pointer to a capdata01 struct to return the data. * @@ -83,7 +86,7 @@ static const struct component_ops lwmi_cd01_component_ops= =3D { * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct ca= pdata01 *output) +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output) { u8 idx; =20 @@ -97,17 +100,17 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 att= ribute_id, struct capdata0 =20 return -EINVAL; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 /** - * lwmi_cd01_cache() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * * Loop through each WMI data block and cache the data. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { int idx; =20 @@ -131,17 +134,17 @@ static int lwmi_cd01_cache(struct lwmi_cd01_priv *pri= v) } =20 /** - * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI d= ata + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { - struct cd01_list *list; + struct cd_list *list; size_t list_size; int count, ret; =20 @@ -163,28 +166,28 @@ static int lwmi_cd01_alloc(struct lwmi_cd01_priv *pri= v) } =20 /** - * lwmi_cd01_setup() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_setup() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI d= ata + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and * cache the data. * * Return: 0 on success, or an error code. */ -static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv) { int ret; =20 - ret =3D lwmi_cd01_alloc(priv); + ret =3D lwmi_cd_alloc(priv); if (ret) return ret; =20 - return lwmi_cd01_cache(priv); + return lwmi_cd_cache(priv); } =20 /** - * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver= notifier. + * lwmi_cd01_notifier_call() - Call method for cd01 notifier. * block call chain. * @nb: The notifier_block registered to lenovo-wmi-events driver. * @action: Unused. @@ -199,17 +202,17 @@ static int lwmi_cd01_notifier_call(struct notifier_bl= ock *nb, unsigned long acti void *data) { struct acpi_bus_event *event =3D data; - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; =20 if (strcmp(event->device_class, ACPI_AC_CLASS) !=3D 0) return NOTIFY_DONE; =20 - priv =3D container_of(nb, struct lwmi_cd01_priv, acpi_nb); + priv =3D container_of(nb, struct lwmi_cd_priv, acpi_nb); =20 switch (event->type) { case ACPI_AC_NOTIFY_STATUS: - ret =3D lwmi_cd01_cache(priv); + ret =3D lwmi_cd_cache(priv); if (ret) return NOTIFY_BAD; =20 @@ -230,10 +233,9 @@ static void lwmi_cd01_unregister(void *data) unregister_acpi_notifier(acpi_nb); } =20 -static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) - +static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; =20 priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); @@ -243,7 +245,7 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, con= st void *context) priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - ret =3D lwmi_cd01_setup(priv); + ret =3D lwmi_cd_setup(priv); if (ret) return ret; =20 @@ -257,27 +259,27 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, c= onst void *context) if (ret) return ret; =20 - return component_add(&wdev->dev, &lwmi_cd01_component_ops); + return component_add(&wdev->dev, &lwmi_cd_component_ops); } =20 -static void lwmi_cd01_remove(struct wmi_device *wdev) +static void lwmi_cd_remove(struct wmi_device *wdev) { - component_del(&wdev->dev, &lwmi_cd01_component_ops); + component_del(&wdev->dev, &lwmi_cd_component_ops); } =20 -static const struct wmi_device_id lwmi_cd01_id_table[] =3D { +static const struct wmi_device_id lwmi_cd_id_table[] =3D { { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, {} }; =20 -static struct wmi_driver lwmi_cd01_driver =3D { +static struct wmi_driver lwmi_cd_driver =3D { .driver =3D { - .name =3D "lenovo_wmi_cd01", + .name =3D "lenovo_wmi_cd", .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, }, - .id_table =3D lwmi_cd01_id_table, - .probe =3D lwmi_cd01_probe, - .remove =3D lwmi_cd01_remove, + .id_table =3D lwmi_cd_id_table, + .probe =3D lwmi_cd_probe, + .remove =3D lwmi_cd_remove, .no_singleton =3D true, }; =20 @@ -290,13 +292,13 @@ static struct wmi_driver lwmi_cd01_driver =3D { */ int lwmi_cd01_match(struct device *dev, void *data) { - return dev->driver =3D=3D &lwmi_cd01_driver.driver; + return dev->driver =3D=3D &lwmi_cd_driver.driver; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD"); =20 -module_wmi_driver(lwmi_cd01_driver); +module_wmi_driver(lwmi_cd_driver); =20 -MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); -MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform= /x86/lenovo/wmi-capdata.h similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.h rename to drivers/platform/x86/lenovo/wmi-capdata.h index bd06c5751f68..2a4746e38ad4 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -2,13 +2,13 @@ =20 /* Copyright (C) 2025 Derek J. Clark */ =20 -#ifndef _LENOVO_WMI_CAPDATA01_H_ -#define _LENOVO_WMI_CAPDATA01_H_ +#ifndef _LENOVO_WMI_CAPDATA_H_ +#define _LENOVO_WMI_CAPDATA_H_ =20 #include =20 struct device; -struct cd01_list; +struct cd_list; =20 struct capdata01 { u32 id; @@ -19,7 +19,7 @@ struct capdata01 { u32 max_value; }; =20 -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct ca= pdata01 *output); +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); int lwmi_cd01_match(struct device *dev, void *data); =20 -#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ +#endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index 2a960b278f11..c6dc1b4cff84 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -34,7 +34,7 @@ #include #include =20 -#include "wmi-capdata01.h" +#include "wmi-capdata.h" #include "wmi-events.h" #include "wmi-gamezone.h" #include "wmi-helpers.h" @@ -74,7 +74,10 @@ enum attribute_property { =20 struct lwmi_om_priv { struct component_master_ops *ops; - struct cd01_list *cd01_list; /* only valid after capdata01 bind */ + + /* only valid after capdata bind */ + struct cd_list *cd01_list; + struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; @@ -576,7 +579,7 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv = *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct cd01_list *tmp_list; + struct cd_list *tmp_list; int ret; =20 ret =3D component_bind_all(dev, &tmp_list); @@ -657,7 +660,7 @@ static struct wmi_driver lwmi_other_driver =3D { =20 module_wmi_driver(lwmi_other_driver); =20 -MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_CD"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 DC7E02253A0; Sat, 22 Nov 2025 18:46:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837170; cv=pass; b=t5bwGuT3x1q8WPm9Ijde1qYzsErAQufl9erKs9R5OPLzaMdHO2jDZh6qoPOA+Y24+KpBKY6SP1wBkGthqloJm+CioSNgH8RiSfOIarItxxNzEPwC1DLI+lH/hGJ/ZSH2zcxfFjxl530T4ctI5ZP/vzLfEZpN4b4cLS0uGPuKXf4= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837170; c=relaxed/simple; bh=R6bEK86MLsSJUuRUri2DSMjcLBuDRtD+dDqZ7HZaK3Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=F06dS7M2LIFB5xa1SUAKcWvaspb82+C3tWeQtKguY01L6qZOo7rxmF/G9UlqrkKVitcFbu5BVLFYP1HUIUy/rKRHC4MUqeAo8wolpdf7pt45newefBFsJZFyXhf9ATx6vNfI0YeweBfQtQuXZpLoDUfmXWeFWGRe5ctUcH/rlME= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=oIXHvODA; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="oIXHvODA" ARC-Seal: i=1; a=rsa-sha256; t=1763837143; cv=none; d=zohomail.com; s=zohoarc; b=RK1ootjQtmLC006KHt3GFZqdWgP0GZV5nD9Q68MjLW3QnnIG/QFUfx+m+XWvfSIHvv50g5GX1wkUh2MIRhGZeapl5R8v2cjfBjNQACrmOxhufI+8M8Oo1tQRWHdtSIgRT++IdIMz49Zj65k2cSHfcOt+5UVilPJwzZRUf6buUQs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837143; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=cYjYa0Xi/nTVhc+RtPAajXYPmFkvODa8yoBIANFxbH8=; b=dR0W0/jUzsEw7v2tEjM0x4zK6Gtyyv5SSJe6BFGx+jZxEc+rTWGmNrd0+jAbzVClmUs+DyvSIaC8AZv/lyvPNEVklbqNst+QO23i/eJjVvf3BrRjAi5VqJaG1M64eKrMF3PwfkT2VMPIrUWeDLP/xvUUgEf+yA3MFy8pHZ+KpQw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837143; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=cYjYa0Xi/nTVhc+RtPAajXYPmFkvODa8yoBIANFxbH8=; b=oIXHvODA8KFek0t02KU32/n31o3OSAOxg5UmVyow3ZWFlgc5X8S1JKraQZq50DGf nbXbLbq4e2b+Lvw84K9m2+bqKMFfehL04Rz/ka9boFr0Z/qsuiRU8wWzq1kQzzmW6Pt j1BumYh+Yl8SFQdB59/qV3OW/jLxUFWKqvR6lTkU= Received: by mx.zohomail.com with SMTPS id 1763837140426720.5598629925264; Sat, 22 Nov 2025 10:45:40 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Date: Sun, 23 Nov 2025 02:44:43 +0800 Message-ID: <20251122184522.18677-4-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" The current implementation are heavily bound to capdata01. Rewrite it so that it is suitable to utilize other Capability Data as well. No functional change intended. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v6: - Fix the error path of lwmi_cd_match_add_all() - IS_ERR(matchptr) =3D> IS_ERR(*matchptr) Changes in v5: - Do not cast pointer to non-pointer or vice versa (thanks kernel test robot) Changes in v4: - Get rid of wmi_has_guid() (thanks Armin Wolf) - More changes in [PATCH v4 6/7] - Prepare for [PATCH v4 6/7] - Move lwmi_cd_match*() forward - Use switch-case in lwmi_cd_remove() Changes in v2: - Fix function parameter 'type' not described in 'lwmi_cd_match' (thanks kernel test bot) --- drivers/platform/x86/lenovo/wmi-capdata.c | 225 +++++++++++++++++----- drivers/platform/x86/lenovo/wmi-capdata.h | 7 +- drivers/platform/x86/lenovo/wmi-other.c | 16 +- 3 files changed, 189 insertions(+), 59 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index c5e74b2bfeb3..e0710354278a 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -12,8 +12,13 @@ * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) + * + * Copyright (C) 2025 Rong Zhang + * - Unified implementation */ =20 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -36,6 +41,23 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 +enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_01, +}; + +#define LWMI_CD_TABLE_ITEM(_type) \ + [_type] =3D { \ + .name =3D #_type, \ + .type =3D _type, \ + } + +static const struct lwmi_cd_info { + const char *name; + enum lwmi_cd_type type; +} lwmi_cd_table[] =3D { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), +}; + struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; @@ -44,15 +66,63 @@ struct lwmi_cd_priv { =20 struct cd_list { struct mutex list_mutex; /* list R/W mutex */ + enum lwmi_cd_type type; u8 count; - struct capdata01 data[]; + + union { + DECLARE_FLEX_ARRAY(struct capdata01, cd01); + }; }; =20 +static struct wmi_driver lwmi_cd_driver; + +/** + * lwmi_cd_match() - Match rule for the master driver. + * @dev: Pointer to the capability data parent device. + * @type: Pointer to capability data type (enum lwmi_cd_type *) to match. + * + * Return: int. + */ +static int lwmi_cd_match(struct device *dev, void *type) +{ + struct lwmi_cd_priv *priv; + + if (dev->driver !=3D &lwmi_cd_driver.driver) + return false; + + priv =3D dev_get_drvdata(dev); + return priv->list->type =3D=3D *(enum lwmi_cd_type *)type; +} + +/** + * lwmi_cd_match_add_all() - Add all match rule for the master driver. + * @master: Pointer to the master device. + * @matchptr: Pointer to the returned component_match pointer. + * + * Adds all component matches to the list stored in @matchptr for the @mas= ter + * device. @matchptr must be initialized to NULL. + */ +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr) +{ + int i; + + if (WARN_ON(*matchptr)) + return; + + for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + component_match_add(master, matchptr, lwmi_cd_match, + (void *)&lwmi_cd_table[i].type); + if (IS_ERR(*matchptr)) + return; + } +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD"); + /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: cd_list object pointer used to return the capability data. + * @data: lwmi_cd_binder object pointer used to return the capability data. * * On lenovo-wmi-other's master bind, provide a pointer to the local capda= ta * list. This is used to call lwmi_cd*_get_data to look up attribute data @@ -64,9 +134,15 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct device *om_dev, void *data) { struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); - struct cd_list **cd_list =3D data; + struct lwmi_cd_binder *binder =3D data; =20 - *cd_list =3D priv->list; + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + binder->cd01_list =3D priv->list; + break; + default: + return -EINVAL; + } =20 return 0; } @@ -76,30 +152,33 @@ static const struct component_ops lwmi_cd_component_op= s =3D { }; =20 /** - * lwmi_cd01_get_data - Get the data of the specified attribute + * lwmi_cd*_get_data - Get the data of the specified attribute * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. - * @output: Pointer to a capdata01 struct to return the data. + * @output: Pointer to a capdata* struct to return the data. * - * Retrieves the capability data 01 struct pointer for the given - * attribute for its specified thermal mode. + * Retrieves the capability data struct pointer for the given + * attribute. * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output) -{ - u8 idx; - - guard(mutex)(&list->list_mutex); - for (idx =3D 0; idx < list->count; idx++) { - if (list->data[idx].id !=3D attribute_id) - continue; - memcpy(output, &list->data[idx], sizeof(list->data[idx])); - return 0; +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ + int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _outp= ut_t *output) \ + { \ + u8 idx; \ + if (WARN_ON(list->type !=3D _cd_type)) \ + return -EINVAL; \ + guard(mutex)(&list->list_mutex); \ + for (idx =3D 0; idx < list->count; idx++) { \ + if (list->_cdxx[idx].id !=3D attribute_id) \ + continue; \ + memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \ + return 0; \ + } \ + return -EINVAL; \ } =20 - return -EINVAL; -} +DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 /** @@ -112,10 +191,21 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_= CD"); */ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { + size_t size; int idx; + void *p; + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + p =3D &priv->list->cd01[0]; + size =3D sizeof(priv->list->cd01[0]); + break; + default: + return -EINVAL; + } =20 guard(mutex)(&priv->list->list_mutex); - for (idx =3D 0; idx < priv->list->count; idx++) { + for (idx =3D 0; idx < priv->list->count; idx++, p +=3D size) { union acpi_object *ret_obj __free(kfree) =3D NULL; =20 ret_obj =3D wmidev_block_query(priv->wdev, idx); @@ -123,11 +213,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return -ENODEV; =20 if (ret_obj->type !=3D ACPI_TYPE_BUFFER || - ret_obj->buffer.length < sizeof(priv->list->data[idx])) + ret_obj->buffer.length < size) continue; =20 - memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, - ret_obj->buffer.length); + memcpy(p, ret_obj->buffer.pointer, size); } =20 return 0; @@ -136,20 +225,28 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { struct cd_list *list; size_t list_size; int count, ret; =20 count =3D wmidev_instance_count(priv->wdev); - list_size =3D struct_size(list, data, count); + + switch (type) { + case LENOVO_CAPABILITY_DATA_01: + list_size =3D struct_size(list, cd01, count); + break; + default: + return -EINVAL; + } =20 list =3D devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); if (!list) @@ -159,6 +256,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) if (ret) return ret; =20 + list->type =3D type; list->count =3D count; priv->list =3D list; =20 @@ -168,6 +266,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) /** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and @@ -175,11 +274,11 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) * * Return: 0 on success, or an error code. */ -static int lwmi_cd_setup(struct lwmi_cd_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { int ret; =20 - ret =3D lwmi_cd_alloc(priv); + ret =3D lwmi_cd_alloc(priv, type); if (ret) return ret; =20 @@ -235,9 +334,13 @@ static void lwmi_cd01_unregister(void *data) =20 static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { + const struct lwmi_cd_info *info =3D context; struct lwmi_cd_priv *priv; int ret; =20 + if (!info) + return -EINVAL; + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -245,30 +348,58 @@ static int lwmi_cd_probe(struct wmi_device *wdev, con= st void *context) priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - ret =3D lwmi_cd_setup(priv); + ret =3D lwmi_cd_setup(priv, info->type); if (ret) - return ret; + goto out; =20 - priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; + switch (info->type) { + case LENOVO_CAPABILITY_DATA_01: + priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 - ret =3D register_acpi_notifier(&priv->acpi_nb); - if (ret) - return ret; + ret =3D register_acpi_notifier(&priv->acpi_nb); + if (ret) + goto out; =20 - ret =3D devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv-= >acpi_nb); - if (ret) - return ret; + ret =3D devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, + &priv->acpi_nb); + if (ret) + goto out; =20 - return component_add(&wdev->dev, &lwmi_cd_component_ops); + ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; + default: + return -EINVAL; + } +out: + if (ret) { + dev_err(&wdev->dev, "failed to register %s: %d\n", + info->name, ret); + } else { + dev_info(&wdev->dev, "registered %s with %u items\n", + info->name, priv->list->count); + } + return ret; } =20 static void lwmi_cd_remove(struct wmi_device *wdev) { - component_del(&wdev->dev, &lwmi_cd_component_ops); + struct lwmi_cd_priv *priv =3D dev_get_drvdata(&wdev->dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + component_del(&wdev->dev, &lwmi_cd_component_ops); + break; + default: + WARN_ON(1); + } } =20 +#define LWMI_CD_WDEV_ID(_type) \ + .guid_string =3D _type##_GUID, \ + .context =3D &lwmi_cd_table[_type] + static const struct wmi_device_id lwmi_cd_id_table[] =3D { - { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; =20 @@ -283,22 +414,10 @@ static struct wmi_driver lwmi_cd_driver =3D { .no_singleton =3D true, }; =20 -/** - * lwmi_cd01_match() - Match rule for the master driver. - * @dev: Pointer to the capability data 01 parent device. - * @data: Unused void pointer for passing match criteria. - * - * Return: int. - */ -int lwmi_cd01_match(struct device *dev, void *data) -{ - return dev->driver =3D=3D &lwmi_cd_driver.driver; -} -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD"); - module_wmi_driver(lwmi_cd_driver); =20 MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index 2a4746e38ad4..d326f9d2d165 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -7,6 +7,7 @@ =20 #include =20 +struct component_match; struct device; struct cd_list; =20 @@ -19,7 +20,11 @@ struct capdata01 { u32 max_value; }; =20 +struct lwmi_cd_binder { + struct cd_list *cd01_list; +}; + +void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); -int lwmi_cd01_match(struct device *dev, void *data); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index c6dc1b4cff84..f2e1e34d58a9 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -579,14 +579,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_pri= v *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct cd_list *tmp_list; + struct lwmi_cd_binder binder =3D { 0 }; int ret; =20 - ret =3D component_bind_all(dev, &tmp_list); + ret =3D component_bind_all(dev, &binder); if (ret) return ret; =20 - priv->cd01_list =3D tmp_list; + priv->cd01_list =3D binder.cd01_list; if (!priv->cd01_list) return -ENODEV; =20 @@ -623,10 +623,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, = const void *context) if (!priv) return -ENOMEM; =20 + /* Sentinel for on-demand ida_free(). */ + priv->ida_id =3D -EIDRM; + priv->wdev =3D wdev; dev_set_drvdata(&wdev->dev, priv); =20 - component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + lwmi_cd_match_add_all(&wdev->dev, &master_match); if (IS_ERR(master_match)) return PTR_ERR(master_match); =20 @@ -639,7 +642,10 @@ static void lwmi_other_remove(struct wmi_device *wdev) struct lwmi_om_priv *priv =3D dev_get_drvdata(&wdev->dev); =20 component_master_del(&wdev->dev, &lwmi_om_master_ops); - ida_free(&lwmi_om_ida, priv->ida_id); + + /* No IDA to free if the driver is never bound to its components. */ + if (priv->ida_id >=3D 0) + ida_free(&lwmi_om_ida, priv->ida_id); } =20 static const struct wmi_device_id lwmi_other_id_table[] =3D { --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 ECED62C11F1; Sat, 22 Nov 2025 18:46:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837174; cv=pass; b=uI8F+ozgKRARJOVdrW42p4bFxdA6uahXcXB248ca4xkPMkGE+r4pp/MHQOMe3PNPtVFVC2D+HNk29yUrAiKJzy0KkaqxoZVWoKieYatvdimBMud3dp/LPCKavsvs6b+SCRmgRwzfQtN0h62SyKESZxZhx7Asc8B9lK1nfaDKQSE= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837174; c=relaxed/simple; bh=FyaVm+F5VOyDUz6saSgEHymw9kC3+V7QvQE0ZwZGPfs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gshGgjRy9s13SYX/kmUKMAvEJ/pZ8Fe7514lM2mC6Gsm/LHBhogYu4Ky/WZoJJE7ASHGtoE6ez6YsMkD++oQBqTssCge+TcPi4cju9SNZBIZsU3yybeepmCIxMBkww0ZpAysOEyo6bCt8z13BCGpboCsyRJAFn1RGVVeR2S5tqQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=H5tL8AR4; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="H5tL8AR4" ARC-Seal: i=1; a=rsa-sha256; t=1763837147; cv=none; d=zohomail.com; s=zohoarc; b=bh5CcABJgeAR3ZMGt5LtayNcrEmcg+fuG3morpwgARVA35xW8WGwhBmakaJ3tXgS5ufMrNV86JLn920s4vr33ynYugMm9SaxOAmzXedozbNTTlrvbgKGUqYkEH0sREqvE5Y1RyPMx19rIgWJYpOCx+xUfIZrMf0PhkLDaIzQ2R0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837147; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=uvtEgNi6kx9+J0uSb5Xup8TbPptYJP87UBd98n9c4ZA=; b=m9quDEZRiJ7FLlsxS+dUStI82kgJqb6PqWoprD1wxY0wRzLNk+al5u5vg8vcFDcvIht3eBrkVkQ8xhHBS9Kq8t0GVvxfA9s03Dk0z8DpwfGbhszrsSnXD1ovwaO4hecjkQnHOOWR3JgHJKy5oB+K8RmYBl2UtylEDQwlpEumTas= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837147; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=uvtEgNi6kx9+J0uSb5Xup8TbPptYJP87UBd98n9c4ZA=; b=H5tL8AR4SiVnsIdmz459aP37LIN3ZWNe5ocVgTlWYEztC3OzTw/pzwUswlUxCpS6 lZRPCkKPvjdbjo8srlxIU66s9naZxBcJreQ3ElkIIAqslagqUJAPLeX63fgf2+7sr+i SI8Nwk6+hlptCoVYVZmqFfP8bSsR0S+bezQ/Dw00= Received: by mx.zohomail.com with SMTPS id 1763837144173759.0532838583723; Sat, 22 Nov 2025 10:45:44 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Date: Sun, 23 Nov 2025 02:44:44 +0800 Message-ID: <20251122184522.18677-5-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Add support for LENOVO_CAPABILITY_DATA_00 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default value. capdata00 always presents on devices with capdata01. lenovo-wmi-other now binds to both (no functional change intended). Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v4: - Rebase on top of changes made to [PATCH v4 3/7] Changes in v2: - Reword documentation (thanks Derek J. Clark) --- .../wmi/devices/lenovo-wmi-other.rst | 15 ++++++++--- drivers/platform/x86/lenovo/wmi-capdata.c | 25 +++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index d7928b8dfb4b..fcad595d49af 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,13 +31,22 @@ under the following path: =20 /sys/class/firmware-attributes/lenovo-wmi-other/attributes// =20 +LENOVO_CAPABILITY_DATA_00 +------------------------- + +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` + +The LENOVO_CAPABILITY_DATA_00 interface provides various information that +does not rely on the gamezone thermal mode. + LENOVO_CAPABILITY_DATA_01 ------------------------- =20 WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` =20 -The LENOVO_CAPABILITY_DATA_01 interface provides information on various -power limits of integrated CPU and GPU components. +The LENOVO_CAPABILITY_DATA_01 interface provides various information that +relies on the gamezone thermal mode, including power limits of integrated +CPU and GPU components. =20 Each attribute has the following properties: - current_value @@ -48,7 +57,7 @@ Each attribute has the following properties: - scalar_increment - type =20 -The following attributes are implemented: +The following firmware-attributes are implemented: - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index e0710354278a..29267c373ab3 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -5,6 +5,9 @@ * Lenovo Capability Data provides information on tunable attributes used = by * the "Other Mode" WMI interface. * + * Capability Data 00 includes if the attribute is supported by the hardwa= re, + * and the default_value. All attributes are independent of thermal modes. + * * Capability Data 01 includes if the attribute is supported by the hardwa= re, * and the default_value, max_value, min_value, and step increment. Each * attribute has multiple pages, one for each of the thermal modes managed= by @@ -36,12 +39,14 @@ =20 #include "wmi-capdata.h" =20 +#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB30= 0E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, }; =20 @@ -55,6 +60,7 @@ static const struct lwmi_cd_info { const char *name; enum lwmi_cd_type type; } lwmi_cd_table[] =3D { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), }; =20 @@ -70,6 +76,7 @@ struct cd_list { u8 count; =20 union { + DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); }; }; @@ -137,6 +144,9 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct lwmi_cd_binder *binder =3D data; =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + binder->cd00_list =3D priv->list; + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list =3D priv->list; break; @@ -178,6 +188,9 @@ static const struct component_ops lwmi_cd_component_ops= =3D { return -EINVAL; \ } =20 +DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); +EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CD"); + DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 @@ -196,6 +209,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) void *p; =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + p =3D &priv->list->cd00[0]; + size =3D sizeof(priv->list->cd00[0]); + break; case LENOVO_CAPABILITY_DATA_01: p =3D &priv->list->cd01[0]; size =3D sizeof(priv->list->cd01[0]); @@ -241,6 +258,9 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enu= m lwmi_cd_type type) count =3D wmidev_instance_count(priv->wdev); =20 switch (type) { + case LENOVO_CAPABILITY_DATA_00: + list_size =3D struct_size(list, cd00, count); + break; case LENOVO_CAPABILITY_DATA_01: list_size =3D struct_size(list, cd01, count); break; @@ -353,6 +373,9 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) goto out; =20 switch (info->type) { + case LENOVO_CAPABILITY_DATA_00: + ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 @@ -386,6 +409,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) struct lwmi_cd_priv *priv =3D dev_get_drvdata(&wdev->dev); =20 switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; @@ -399,6 +423,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) .context =3D &lwmi_cd_table[_type] =20 static const struct wmi_device_id lwmi_cd_id_table[] =3D { + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index d326f9d2d165..a6d006ef458f 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -11,6 +11,12 @@ struct component_match; struct device; struct cd_list; =20 +struct capdata00 { + u32 id; + u32 supported; + u32 default_value; +}; + struct capdata01 { u32 id; u32 supported; @@ -21,10 +27,12 @@ struct capdata01 { }; =20 struct lwmi_cd_binder { + struct cd_list *cd00_list; struct cd_list *cd01_list; }; =20 void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); +int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 52C022C11EF; Sat, 22 Nov 2025 18:46:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837177; cv=pass; b=V0otMirUHj1t+uS52fjMzx+GqKKpNhvkxGfUwEiyrn7cUjBHrXE52YBuuAU6xu9UbLhRA58sGxFN6rnrse6Z/a9eOsh1kK15hADeFmjPye/Q5b916aKKqiBPuRsW26ruXJZQ16sweicM8k5LoXRQ9vHHSGu3cSW4UhRCfRqJRzc= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837177; c=relaxed/simple; bh=Y3CYmEcFGscCiNGtNL4ZZrOKD7U2t3swzn1mIdHxMhQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AAr78Aj4PWes4oP/oqAmAWfvxUak92vSuKyzs9qly+pjPfu8S3Aqm+jYUzBLre0W2/CiP3ybpvKaqyYTuY/X9ptlifl3M71aF2MN6tKqr/zPMrlYhQXdUqx6dcJEvZUv/x/G1Xt9ucEJr0ieboBnWEMk4qr7AQnuBC95gdqP8+8= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=UBtk1tZD; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="UBtk1tZD" ARC-Seal: i=1; a=rsa-sha256; t=1763837151; cv=none; d=zohomail.com; s=zohoarc; b=Z0GP2JxxRrd9UJmUqi+CJH+2n1sk/bYHPRMnbD3R/SIICOh8ci3nqpPxJot437JRPZyKpAvpY/d+IwTMHxoKlmfErizSm1DaPGRhiS+80gXisvurxHcKi2vpDmgI7B5+7R8mvh/xAULzwGFK4B5HmcQI1fvVySyJmEp78nF/Gcw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837151; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=9iWR5a2IqOeOnfpyv6fZQLq4ARu+wp6y81duiOqWSpA=; b=GMS3HTHvjfMsyG6oPekqOsHC5lG0EOuUr/yqEoreCEG/6t67No4h69Dwz2NoV4vTLMhqZGsQKtGpD16y64zIEb53J4T8NOGhCTGneB6vtpc6bUgYsMYbXH5rIS3b0W1WKaomcCVIuM0ijqg83CzAEXdul3OfSJdw5XwxAdRQrbc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837151; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=9iWR5a2IqOeOnfpyv6fZQLq4ARu+wp6y81duiOqWSpA=; b=UBtk1tZDmP0rLSaeV766uCrkXsQD7SN5ZPrYqUeI1EaSG3U1KNYPM0W5UlNaYTVx nSCZtoXLawvHCKt/Vz1jtaliJovkOMchLWw6l5xKvkAMIXV8AmpjRPvSiLXZvHJTuFA 9U0mfDKGYVn+gnipGDJ//WPPXjQNcssOxlS5NLY8= Received: by mx.zohomail.com with SMTPS id 1763837147929485.8294553011483; Sat, 22 Nov 2025 10:45:47 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Date: Sun, 23 Nov 2025 02:44:45 +0800 Message-ID: <20251122184522.18677-6-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Add support for LENOVO_FAN_TEST_DATA WMI data block. Provides an interface for querying the min/max fan speed RPM (reference data) of a given fan ID. This interface is optional. Hence, it does not bind to lenovo-wmi-other and is not registered as a component for the moment. Appropriate binding will be implemented in the subsequent patch. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark --- Changes in v4: - Rebase on top of changes made to [PATCH v4 3/7] - Do not register it as a component until [PATCH v4 6/7] Changes in v2: - Reword documentation --- .../wmi/devices/lenovo-wmi-other.rst | 17 +++ drivers/platform/x86/lenovo/wmi-capdata.c | 102 ++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 7 ++ 3 files changed, 126 insertions(+) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index fcad595d49af..821282e07d93 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -62,6 +62,13 @@ The following firmware-attributes are implemented: - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking =20 +LENOVO_FAN_TEST_DATA +------------------------- + +WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` + +The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of +cooling fans. =20 WMI interface description =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D @@ -115,3 +122,13 @@ data using the `bmfdec `_ utility: [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize= ")] uint8 DefaultValue[]; }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("De= finition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")] + class LENOVO_FAN_TEST_DATA { + [key, read] string InstanceName; + [read] boolean Active; + [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans; + [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] u= int32 FanId[]; + [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("Num= OfFans")] uint32 FanMaxSpeed[]; + [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("Num= OfFans")] uint32 FanMinSpeed[]; + }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index 29267c373ab3..e6392357395c 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -13,6 +13,10 @@ * attribute has multiple pages, one for each of the thermal modes managed= by * the Gamezone interface. * + * Fan Test Data includes the max/min fan speed RPM for each fan. This is + * reference data for self-test. If the fan is in good condition, it is ca= pable + * to spin faster than max RPM or slower than min RPM. + * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) * @@ -41,6 +45,7 @@ =20 #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB30= 0E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" +#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" =20 #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 @@ -48,6 +53,7 @@ enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, + LENOVO_FAN_TEST_DATA, }; =20 #define LWMI_CD_TABLE_ITEM(_type) \ @@ -62,6 +68,7 @@ static const struct lwmi_cd_info { } lwmi_cd_table[] =3D { LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), + LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA), }; =20 struct lwmi_cd_priv { @@ -78,6 +85,7 @@ struct cd_list { union { DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); + DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan); }; }; =20 @@ -117,6 +125,10 @@ void lwmi_cd_match_add_all(struct device *master, stru= ct component_match **match return; =20 for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + /* Skip optional interfaces. */ + if (lwmi_cd_table[i].type =3D=3D LENOVO_FAN_TEST_DATA) + continue; + component_match_add(master, matchptr, lwmi_cd_match, (void *)&lwmi_cd_table[i].type); if (IS_ERR(*matchptr)) @@ -194,6 +206,9 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CD= "); DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD"); =20 +DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); +EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CD"); + /** * lwmi_cd_cache() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. @@ -217,6 +232,9 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) p =3D &priv->list->cd01[0]; size =3D sizeof(priv->list->cd01[0]); break; + case LENOVO_FAN_TEST_DATA: + /* Done by lwmi_cd_alloc() =3D> lwmi_cd_fan_list_alloc_cache(). */ + return 0; default: return -EINVAL; } @@ -239,6 +257,78 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return 0; } =20 +/** + * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list + * @priv: lenovo-wmi-capdata driver data. + * @listptr: Pointer to returned cd_list pointer. + * + * Return: count of fans found, or an error. + */ +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct = cd_list **listptr) +{ + u32 count, *fan_ids, *fan_min_rpms, *fan_max_rpms; + union acpi_object *ret_obj __free(kfree) =3D NULL; + struct block { u32 nr; u32 data[]; } *block; + struct cd_list *list; + size_t size; + int idx; + + ret_obj =3D wmidev_block_query(priv->wdev, 0); + if (!ret_obj) + return -ENODEV; + + /* + * This is usually caused by a dummy ACPI method. Do not return an error + * as failing to probe this device will result in master driver being + * unbound - this behavior aligns with lwmi_cd_cache(). + */ + if (ret_obj->type !=3D ACPI_TYPE_BUFFER) { + count =3D 0; + goto alloc; + } + + size =3D ret_obj->buffer.length; + block =3D (struct block *)ret_obj->buffer.pointer; + + count =3D size >=3D sizeof(*block) ? block->nr : 0; + if (size < struct_size(block, data, count * 3)) { + dev_warn(&priv->wdev->dev, + "incomplete fan test data block: %zu < %zu, ignoring\n", + size, struct_size(block, data, count * 3)); + count =3D 0; + } + + if (count =3D=3D 0) + goto alloc; + + if (count > U8_MAX) { + dev_warn(&priv->wdev->dev, + "too many fans reported: %u > %u, truncating\n", + count, U8_MAX); + count =3D U8_MAX; + } + + fan_ids =3D &block->data[0]; + fan_max_rpms =3D &block->data[count]; + fan_min_rpms =3D &block->data[count * 2]; + +alloc: + list =3D devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count),= GFP_KERNEL); + if (!list) + return -ENOMEM; + + for (idx =3D 0; idx < count; idx++) { + list->cd_fan[idx] =3D (struct capdata_fan) { + .id =3D fan_ids[idx], + .min_rpm =3D fan_min_rpms[idx], + .max_rpm =3D fan_max_rpms[idx], + }; + } + + *listptr =3D list; + return count; +} + /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. @@ -264,6 +354,12 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, en= um lwmi_cd_type type) case LENOVO_CAPABILITY_DATA_01: list_size =3D struct_size(list, cd01, count); break; + case LENOVO_FAN_TEST_DATA: + count =3D lwmi_cd_fan_list_alloc_cache(priv, &list); + if (count < 0) + return count; + + goto got_list; default: return -EINVAL; } @@ -272,6 +368,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enu= m lwmi_cd_type type) if (!list) return -ENOMEM; =20 +got_list: ret =3D devm_mutex_init(&priv->wdev->dev, &list->list_mutex); if (ret) return ret; @@ -390,6 +487,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) =20 ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; + case LENOVO_FAN_TEST_DATA: + goto out; default: return -EINVAL; } @@ -413,6 +512,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev) case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; + case LENOVO_FAN_TEST_DATA: + break; default: WARN_ON(1); } @@ -425,6 +526,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) static const struct wmi_device_id lwmi_cd_id_table[] =3D { { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, + { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) }, {} }; =20 diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index a6d006ef458f..38af4c4e4ef4 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -26,6 +26,12 @@ struct capdata01 { u32 max_value; }; =20 +struct capdata_fan { + u32 id; + u32 min_rpm; + u32 max_rpm; +}; + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; @@ -34,5 +40,6 @@ struct lwmi_cd_binder { void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capd= ata01 *output); +int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct ca= pdata_fan *output); =20 #endif /* !_LENOVO_WMI_CAPDATA_H_ */ --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 E4FE42C11EF; Sat, 22 Nov 2025 18:46:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837185; cv=pass; b=Lv4Yo91+ZdRjvO8dt8h6XRxMQhGVmFYZiVEw4BP6BvHrLzMlxCBD9As7GQEP7w/HWLRp3Kq5JaJ90plIQFv5d1FnHOiLT3qGggztXGm+Jwe4Pc2/lQGiQ12/8rw5ybdyXPQj0o9LJC1XTcL/LJiKgokd9X9L3Q74OpYUMXCYMpE= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837185; c=relaxed/simple; bh=iPhCso/LVYGWPOzGKw9NAbs6YAVKFE+s9liGrr2SXdY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=o9QlGX9DuLD/q1rUcwHRAOGa5rtt+EqD2bEn95yqyU3dvBKvnj7O5G/2KEpdMT8NZkMHxLgVfIkNoUE8aVn2/Zo+QexCZZtbCjlwC8prp4/d2XHotdWqR9/fUshy1f/p0IknImrfYF8gkDn0hQ1tN+s3IJSoNwidLyc4HEpQ1qE= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=ztrEKM9Q; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="ztrEKM9Q" ARC-Seal: i=1; a=rsa-sha256; t=1763837159; cv=none; d=zohomail.com; s=zohoarc; b=OBvqdr2MTG/8rGdHH14BKpJnK4fu3seMdZx8qfMRf8eQBrr3l4A2NEbB0yldwpABG3yjZJtiMX3teeCt4WuWHfQjrguj5rzOnFiV73FebJIR/oMAdoxLL3k4H864ZEnJLg5wwm2P+0LkqM0sNDMgU0Ai1dS19Cmmx0FftOz+ubY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837159; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=x9q0MzUSsh4BhPu/1z+QXN1tUnPsTWNI1ZB6euxWleM=; b=hAyieGcitq++b4NoYSqTupKl4B01IHlHBAuATDXX0Vth10uDaar5AVdiu8Ehke+lqB8g7kyBFCRRBgzu/H1ANOWxvyOfK20D4u4XhE1TjQcfynl+fr07LHRQbsdMtBgUva8r1FXKH/zZJM0bhfI+O9n5qvT0+aWW5Yur1d/awu8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837159; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=x9q0MzUSsh4BhPu/1z+QXN1tUnPsTWNI1ZB6euxWleM=; b=ztrEKM9Qo+eQIdYaTUOxh6K3ENZRaG/NQJTgyIrhCAHPtypVxaW7ryHxsJ6jaZr2 ieEN7BA5QNm40CSANVUpHuYCGHYIlNbhLJMDcmx86uI6e0yIWwGBBaTTVuvg1teWtB1 SFiv1BIKe/vi6/3iVcc6VvnwUPB32zv718keh+9A= Received: by mx.zohomail.com with SMTPS id 1763837156989603.4386805202904; Sat, 22 Nov 2025 10:45:56 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data Date: Sun, 23 Nov 2025 02:44:46 +0800 Message-ID: <20251122184522.18677-7-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" A capdata00 attribute (0x04050000) describes the presence of Fan Test Data. Query it, and bind Fan Test Data as a component of capdata00 accordingly. The component master of capdata00 may pass a callback while binding to retrieve fan info from Fan Test Data. Summarizing this scheme: lenovo-wmi-other <-> capdata00 <-> capdata_fan |- master |- component |- sub-master |- sub-component The callback will be called once both the master and the sub-component are bound to the sub-master (component). This scheme is essential to solve these issues: - The component framework only supports one aggregation per master - A binding is only established until all components are found - The Fan Test Data interface may be missing on some devices - To get rid of queries for the presense of WMI GUIDs capdata00 is registered as a component and a sub-master on probe, instead of chaining the registerations in one's bind callback. This is because calling (un)registration methods of the component framework causes deadlock in (un)bind callbacks, i.e., it's impossible to register capdata00 as a sub-master/component in its component/sub-master bind callback, and vice versa. Signed-off-by: Rong Zhang --- Changes in v6: - Fix the error path of component_add(capdata00) - Elaborate the design in commit message Changes in v5: - Fix missing include (thanks kernel test robot) Changes in v4: - New patch in the series (thanks Armin Wolf's inspiration) - Get rid of wmi_has_guid() (see also [PATCH v4 3/7]) --- drivers/platform/x86/lenovo/wmi-capdata.c | 265 +++++++++++++++++++++- drivers/platform/x86/lenovo/wmi-capdata.h | 20 ++ drivers/platform/x86/lenovo/wmi-other.c | 5 - 3 files changed, 283 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x= 86/lenovo/wmi-capdata.c index e6392357395c..8760f8c071ca 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -27,6 +27,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt =20 #include +#include #include #include #include @@ -50,10 +51,17 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 =20 +#define LWMI_FEATURE_ID_FAN_TEST 0x05 + +#define LWMI_ATTR_ID_FAN_TEST \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST)) + enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, LENOVO_FAN_TEST_DATA, + CD_TYPE_NONE =3D -1, }; =20 #define LWMI_CD_TABLE_ITEM(_type) \ @@ -75,6 +83,20 @@ struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; struct cd_list *list; + + /* + * A capdata device may be a component master of another capdata device. + * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan + * |- master |- component + * |- sub-master + * |- sub-component + */ + struct lwmi_cd_sub_master_priv { + struct device *master_dev; + cd_list_cb_t master_cb; + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-c= omponent. */ + bool registered; /* Has the sub-master been registere= d? */ + } *sub_master; }; =20 struct cd_list { @@ -125,7 +147,7 @@ void lwmi_cd_match_add_all(struct device *master, struc= t component_match **match return; =20 for (i =3D 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { - /* Skip optional interfaces. */ + /* Skip sub-components. */ if (lwmi_cd_table[i].type =3D=3D LENOVO_FAN_TEST_DATA) continue; =20 @@ -137,6 +159,56 @@ void lwmi_cd_match_add_all(struct device *master, stru= ct component_match **match } EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD"); =20 +/** + * lwmi_cd_call_master_cb() - Call the master callback for the sub-compone= nt. + * @priv: Pointer to the capability data private data. + * + * Call the master callback and pass the sub-component list to it if the + * dependency chain (master <-> sub-master <-> sub-component) is complete. + */ +static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv) +{ + struct cd_list *sub_component_list =3D priv->sub_master->sub_component_li= st; + + /* + * Call the callback only if the dependency chain is ready: + * - Binding between master and sub-master: fills master_dev and master_cb + * - Binding between sub-master and sub-component: fills sub_component_li= st + * + * If a binding has been unbound before the other binding is bound, the + * corresponding members filled by the former are guaranteed to be cleare= d. + * + * This function is only called in bind callbacks, and the component + * framework guarantees bind/unbind callbacks may never execute + * simultaneously, which implies that it's impossible to have a race + * condition. + * + * Hence, this check is sufficient to ensure that the callback is called + * at most once and with the correct state, without relying on a specific + * sequence of binding establishment. + */ + if (!sub_component_list || + !priv->sub_master->master_dev || + !priv->sub_master->master_cb) + return; + + if (PTR_ERR(sub_component_list) =3D=3D -ENODEV) + sub_component_list =3D NULL; + else if (WARN_ON(IS_ERR(sub_component_list))) + return; + + priv->sub_master->master_cb(priv->sub_master->master_dev, + sub_component_list); + + /* + * Prevent "unbind and rebind" sequences from userspace from calling the + * callback twice. + */ + priv->sub_master->master_cb =3D NULL; + priv->sub_master->master_dev =3D NULL; + priv->sub_master->sub_component_list =3D NULL; +} + /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. @@ -147,6 +219,8 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI= _CD"); * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * + * If cd_dev is a sub-master, try to call the master callback. + * * Return: 0 */ static int lwmi_cd_component_bind(struct device *cd_dev, @@ -158,6 +232,11 @@ static int lwmi_cd_component_bind(struct device *cd_de= v, switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: binder->cd00_list =3D priv->list; + + priv->sub_master->master_dev =3D om_dev; + priv->sub_master->master_cb =3D binder->cd_fan_list_cb; + lwmi_cd_call_master_cb(priv); + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list =3D priv->list; @@ -169,8 +248,167 @@ static int lwmi_cd_component_bind(struct device *cd_d= ev, return 0; } =20 +/** + * lwmi_cd_component_unbind() - Unbind component to master device. + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. + * @data: Unused. + * + * If cd_dev is a sub-master, clear the collected data from the master dev= ice to + * prevent the binding establishment between the sub-master and the sub- + * component (if it's about to happen) from calling the master callback. + */ +static void lwmi_cd_component_unbind(struct device *cd_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(cd_dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + priv->sub_master->master_dev =3D NULL; + priv->sub_master->master_cb =3D NULL; + return; + default: + return; + } +} + static const struct component_ops lwmi_cd_component_ops =3D { .bind =3D lwmi_cd_component_bind, + .unbind =3D lwmi_cd_component_unbind, +}; + +/** + * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device + * @dev: The sub-master capdata basic device. + * + * Call component_bind_all to bind the sub-component device to the sub-mas= ter + * device. On success, collect the pointer to the sub-component list and t= ry + * to call the master callback. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_bind(struct device *dev) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(dev); + struct cd_list *sub_component_list; + int ret; + + ret =3D component_bind_all(dev, &sub_component_list); + if (ret) + return ret; + + priv->sub_master->sub_component_list =3D sub_component_list; + lwmi_cd_call_master_cb(priv); + + return 0; +} + +/** + * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device + * @dev: The sub-master capdata basic device + * + * Clear the collected pointer to the sub-component list to prevent the bi= nding + * establishment between the sub-master and the sub-component (if it's abo= ut to + * happen) from calling the master callback. Then, call component_unbind_a= ll to + * unbind the sub-component device from the sub-master device. + */ +static void lwmi_cd_sub_master_unbind(struct device *dev) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(dev); + + priv->sub_master->sub_component_list =3D NULL; + + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_cd_sub_master_ops =3D { + .bind =3D lwmi_cd_sub_master_bind, + .unbind =3D lwmi_cd_sub_master_unbind, +}; + +/** + * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component + * @priv: Pointer to the sub-master capdata device private data. + * @sub_component_type: Type of the sub-component. + * + * Match the sub-component type and register the current capdata device as= a + * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the s= ub- + * component as non-existent without registering sub-master. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, + enum lwmi_cd_type sub_component_type) +{ + struct component_match *master_match =3D NULL; + int ret; + + priv->sub_master =3D devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_mas= ter), GFP_KERNEL); + if (!priv->sub_master) + return -ENOMEM; + + if (sub_component_type =3D=3D CD_TYPE_NONE) { + /* The master callback will be called with NULL on bind. */ + priv->sub_master->sub_component_list =3D ERR_PTR(-ENODEV); + priv->sub_master->registered =3D false; + return 0; + } + + /* + * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack + * data cannot be used here. Steal one from lwmi_cd_table. + */ + component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match, + (void *)&lwmi_cd_table[sub_component_type].type); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + ret =3D component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_ma= ster_ops, + master_match); + if (ret) + return ret; + + priv->sub_master->registered =3D true; + return 0; +} + +/** + * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered + * @priv: Pointer to the sub-master capdata device private data. + */ +static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) +{ + if (priv->sub_master->registered) { + component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); + priv->sub_master->registered =3D false; + } +} + +/** + * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device. + * @sc_dev: Pointer to the sub-component capdata parent device. + * @sm_dev: Pointer to the sub-master capdata parent device. + * @data: Pointer used to return the capability data list pointer. + * + * On sub-master's bind, provide a pointer to the local capdata list. + * This is used by the sub-master to call the master callback. + * + * Return: 0 + */ +static int lwmi_cd_sub_component_bind(struct device *sc_dev, + struct device *sm_dev, void *data) +{ + struct lwmi_cd_priv *priv =3D dev_get_drvdata(sc_dev); + struct cd_list **listp =3D data; + + *listp =3D priv->list; + + return 0; +} + +static const struct component_ops lwmi_cd_sub_component_ops =3D { + .bind =3D lwmi_cd_sub_component_bind, }; =20 /** @@ -470,9 +708,28 @@ static int lwmi_cd_probe(struct wmi_device *wdev, cons= t void *context) goto out; =20 switch (info->type) { - case LENOVO_CAPABILITY_DATA_00: + case LENOVO_CAPABILITY_DATA_00: { + enum lwmi_cd_type sub_component_type =3D LENOVO_FAN_TEST_DATA; + struct capdata00 capdata00; + + ret =3D lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00= ); + if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { + dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); + sub_component_type =3D CD_TYPE_NONE; + } + + /* Sub-master (capdata00) <-> sub-component (capdata_fan) */ + ret =3D lwmi_cd_sub_master_add(priv, sub_component_type); + if (ret) + goto out; + + /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */ ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); + if (ret) + lwmi_cd_sub_master_del(priv); + goto out; + } case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call =3D lwmi_cd01_notifier_call; =20 @@ -488,6 +745,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const= void *context) ret =3D component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; case LENOVO_FAN_TEST_DATA: + ret =3D component_add(&wdev->dev, &lwmi_cd_sub_component_ops); goto out; default: return -EINVAL; @@ -509,10 +767,13 @@ static void lwmi_cd_remove(struct wmi_device *wdev) =20 switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: + lwmi_cd_sub_master_del(priv); + fallthrough; case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; case LENOVO_FAN_TEST_DATA: + component_del(&wdev->dev, &lwmi_cd_sub_component_ops); break; default: WARN_ON(1); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x= 86/lenovo/wmi-capdata.h index 38af4c4e4ef4..59ca3b3e5760 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -5,8 +5,20 @@ #ifndef _LENOVO_WMI_CAPDATA_H_ #define _LENOVO_WMI_CAPDATA_H_ =20 +#include #include =20 +#define LWMI_SUPP_VALID BIT(0) +#define LWMI_SUPP_MAY_GET (LWMI_SUPP_VALID | BIT(1)) +#define LWMI_SUPP_MAY_SET (LWMI_SUPP_VALID | BIT(2)) + +#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) + +#define LWMI_DEVICE_ID_FAN 0x04 + struct component_match; struct device; struct cd_list; @@ -32,9 +44,17 @@ struct capdata_fan { u32 max_rpm; }; =20 +typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd= _list); + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; + /* + * May be called during or after the bind callback. + * Will be called with NULL if capdata_fan does not exist. + * The pointer is only valid in the callback; never keep it for later use! + */ + cd_list_cb_t cd_fan_list_cb; }; =20 void lwmi_cd_match_add_all(struct device *master, struct component_match *= *matchptr); diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index f2e1e34d58a9..b3adcc2804fa 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -54,11 +54,6 @@ #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 =20 -#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) -#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) -#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) -#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) - #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" =20 static BLOCKING_NOTIFIER_HEAD(om_chain_head); --=20 2.51.0 From nobody Tue Dec 2 01:06:11 2025 Received: from sender3-op-o15.zoho.com (sender3-op-o15.zoho.com [136.143.184.15]) (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 B35C02C08C5; Sat, 22 Nov 2025 18:46:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.184.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837191; cv=pass; b=e4CwqSjjQDsq87MNlSeLCjPvvmq5i+kNz7eCRCAPeai2HCWkXv3nVj0pFaScyBBiGj1LV2x/FYES3A2UxV3sEtk+uYTFtPXgwrPJF4XCcvU717Uxt2xHrvU+pkZo6iUk/CQGM6l90Yg4EkCnmnw4fxDZAiYx6oOfc2ke2Lu3FUY= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763837191; c=relaxed/simple; bh=a8O2G8ITCngo3T2nOs2WRxXLLj2GbaNc02+hEj0guhc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ni2Ti1Mb+uXqTTZU15c7srh54CJMHy6Vc7K86VP6JqMbGUoNujrqjKjnoZNwgEokAvWZKtO+09PJqYaaBPKPRoBk2EEALqIm5Qlk4Evv5sX8/cc5FE4t5xvgF5pbkRq8LutBOBgGgIBYVsYVhfbCHgiia6dXSm2hnAfqD2aUG1E= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b=ZfmhEZ8z; arc=pass smtp.client-ip=136.143.184.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=rong.moe header.i=i@rong.moe header.b="ZfmhEZ8z" ARC-Seal: i=1; a=rsa-sha256; t=1763837163; cv=none; d=zohomail.com; s=zohoarc; b=jbRsEIFpTdOBQq44rgs/g7Dvig7osrZ5PGqcX7QjvFI5PkurRoyhxYueOTVdszOuoiyZDhIUbB+/wTO7wqyB7VZ1llry5joeutoVwAXBe7Fauqx7nu67e6cINR10y5bQXisE0iVf6T7LNK/IuIaSHPypSnqJQ+yCvqVZvBTzH3w= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1763837163; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=CsYfkL16YUxVV4VyRd2jVtNTRf5fHtD24pJTSwsUTIY=; b=noWzKr3a+nVGN8MgkSrvv6gXrP9wSso+7eq+G1rtGwsyh/KuaQleGO56N7pbch+6TGLuBdMgd9rMU7cbgnrWthzWIEB+dqUtaC9hVEoE2ugE7+x6S8aEcb/FzW04bhoqq0yP8rPSdQuBbgV+Ez8BICxLuuU8nssXFZtIL4UdNAs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1763837163; s=zmail; d=rong.moe; i=i@rong.moe; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=CsYfkL16YUxVV4VyRd2jVtNTRf5fHtD24pJTSwsUTIY=; b=ZfmhEZ8zkZzctNg/qcXOGEj8VqP5z26R4IP94gc5nLLCYTkzMFlqMJG4MZtUTh6m Gwv7hikuhGkFg4qX/eKKA7Vgm3REi+MWvmMk6ulwK3HaRV7qWOMLldDgpkeVr4d6L9/ Ju5cewZPbCUjMRRGoVX9ZLrCNRLVMFAqlTduyq/E= Received: by mx.zohomail.com with SMTPS id 1763837161003958.1890809203555; Sat, 22 Nov 2025 10:46:01 -0800 (PST) From: Rong Zhang To: Mark Pearson , "Derek J. Clark" , Armin Wolf , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Rong Zhang , Guenter Roeck , platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org Subject: [PATCH v6 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Date: Sun, 23 Nov 2025 02:44:47 +0800 Message-ID: <20251122184522.18677-8-i@rong.moe> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251122184522.18677-1-i@rong.moe> References: <20251122184522.18677-1-i@rong.moe> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Register an HWMON device for fan reporting/tuning according to Capability Data 00 (capdata00) and Fan Test Data (capdata_fan) provided by lenovo-wmi-capdata. The corresponding HWMON nodes are: - fanX_enable: enable/disable the fan (tunable) - fanX_input: current RPM - fanX_max: maximum RPM - fanX_min: minimum RPM - fanX_target: target RPM (tunable) Information from capdata00 and capdata_fan are used to control the visibility and constraints of HWMON attributes. Fan info from capdata00 is collected on bind, while fan info from capdata_fan is collected in a callback. Once all fan info is collected, register the HWMON device. Signed-off-by: Rong Zhang --- Changes in v4: - Rework HWMON registration due to the introduction of [PATCH v4 6/7] - Collect fan info from capdata00 and capdata_fan separately - Use a callback to collect fan info from capdata_fan - Trigger HWMON registration only if all fan info is collected - Do not check 0x04050000.supported, implied by the presense of capdata_fan - Drop Reviewed-by & Tested-by due to the changes, please review & test Changes in v3: - Reword documentation (thanks Derek J. Clark) Changes in v2: - Define 4 fan channels instead of 2 (thanks Derek J. Clark) - Squash min/max reporting patch into this one (ditto) - Query 0x04050000 for interface availability (ditto) - New parameter "expose_all_fans" to skip this check - Enforce min/max RPM constraint on set (ditto) - New parameter "relax_fan_constraint" to disable this behavior - Drop parameter "ignore_fan_cap", superseded by the next one - New parameter "expose_all_fans" to expose fans w/o such data - Assume auto mode on probe (ditto) - Reword documentation (ditto) - Do not register HWMON device if no fan can be exposed - fanX_target: Return -EBUSY instead of raw target value when fan stops --- .../wmi/devices/lenovo-wmi-other.rst | 11 + drivers/platform/x86/lenovo/Kconfig | 1 + drivers/platform/x86/lenovo/wmi-other.c | 485 +++++++++++++++++- 3 files changed, 487 insertions(+), 10 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation= /wmi/devices/lenovo-wmi-other.rst index 821282e07d93..bd1d733ff286 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,6 +31,8 @@ under the following path: =20 /sys/class/firmware-attributes/lenovo-wmi-other/attributes// =20 +Additionally, this driver also exports attributes to HWMON. + LENOVO_CAPABILITY_DATA_00 ------------------------- =20 @@ -39,6 +41,11 @@ WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` The LENOVO_CAPABILITY_DATA_00 interface provides various information that does not rely on the gamezone thermal mode. =20 +The following HWMON attributes are implemented: + - fanX_enable: enable/disable the fan (tunable) + - fanX_input: current RPM + - fanX_target: target RPM (tunable) + LENOVO_CAPABILITY_DATA_01 ------------------------- =20 @@ -70,6 +77,10 @@ WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of cooling fans. =20 +The following HWMON attributes are implemented: + - fanX_max: maximum RPM + - fanX_min: minimum RPM + WMI interface description =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D =20 diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/len= ovo/Kconfig index fb96a0f908f0..be9af0451146 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -263,6 +263,7 @@ config LENOVO_WMI_GAMEZONE config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI + select HWMON select FW_ATTR_CLASS select LENOVO_WMI_DATA select LENOVO_WMI_EVENTS diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86= /lenovo/wmi-other.c index b3adcc2804fa..ce1ca13db4b5 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -14,7 +14,16 @@ * These attributes typically don't fit anywhere else in the sysfs and are= set * in Windows using one of Lenovo's multiple user applications. * + * Additionally, this driver also exports tunable fan speed RPM to HWMON. + * Min/max RPM are also provided for reference. + * * Copyright (C) 2025 Derek J. Clark + * - fw_attributes + * - binding to Capability Data 01 + * + * Copyright (C) 2025 Rong Zhang + * - HWMON + * - binding to Capability Data 00 and Fan */ =20 #include @@ -25,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -49,12 +59,26 @@ #define LWMI_FEATURE_ID_CPU_SPL 0x02 #define LWMI_FEATURE_ID_CPU_FPPT 0x03 =20 +#define LWMI_FEATURE_ID_FAN_RPM 0x03 + #define LWMI_TYPE_ID_NONE 0x00 =20 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 =20 +#define LWMI_FAN_ID_BASE 1 +#define LWMI_FAN_NR 4 +#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) + +#define LWMI_ATTR_ID_FAN_RPM(x) \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) + +#define LWMI_FAN_STOP_RPM 1 + #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" +#define LWMI_OM_HWMON_NAME "lenovo_wmi_other" =20 static BLOCKING_NOTIFIER_HEAD(om_chain_head); static DEFINE_IDA(lwmi_om_ida); @@ -71,15 +95,439 @@ struct lwmi_om_priv { struct component_master_ops *ops; =20 /* only valid after capdata bind */ + struct cd_list *cd00_list; struct cd_list *cd01_list; =20 + struct device *hwmon_dev; struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; struct wmi_device *wdev; int ida_id; + + struct fan_info { + u32 supported; + u32 last_target; + long min_rpm; + long max_rpm; + } fan_info[LWMI_FAN_NR]; + struct { + bool capdata00_collected : 1; + bool capdata_fan_collected : 1; + } fan_flags; +}; + +/* + * Visibility of fan channels: + * + * +-------------------+---------+------------------+---------------------= --+------------+ + * | | default | +expose_all_fans | +relax_fan_constrain= t | +both | + * +-------------------+---------+------------------+---------------------= --+------------+ + * | canonical | RW | RW | RW+relaxed = | RW+relaxed | + * +-------------------+---------+------------------+---------------------= --+------------+ + * | -capdata_fan[idx] | N | RO | N = | RW+relaxed | + * +-------------------+---------+------------------+---------------------= --+------------+ + * + * Note: + * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposin= g a channel. + * 2. -capdata_fan implies -capdata_fan[idx]. + */ +static bool expose_all_fans; +module_param(expose_all_fans, bool, 0444); +MODULE_PARM_DESC(expose_all_fans, + "This option skips some capability checks and solely relies on per-channe= l ones " + "to expose fan attributes. Use with caution."); + +static bool relax_fan_constraint; +module_param(relax_fan_constraint, bool, 0444); +MODULE_PARM_DESC(relax_fan_constraint, + "Do not enforce fan RPM constraint (min/max RPM) " + "and enables fan tuning when such data is missing. " + "Enabling this may results in HWMON attributes being out-of-sync. Use wit= h caution."); + +/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON (component: lenovo-wmi-capdata 00 & fan)= =3D=3D=3D=3D=3D=3D=3D=3D */ + +/** + * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan + * @priv: Driver private data structure + * @channel: Fan channel index (0-based) + * @val: Pointer to value (input for set, output for get) + * @set: True to set value, false to get value + * + * Communicates with WMI interface to either retrieve current fan RPM + * or set target fan RPM. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32= *val, bool set) +{ + struct wmi_method_args_32 args; + u32 method_id, retval; + int err; + + method_id =3D set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET; + args.arg0 =3D LWMI_ATTR_ID_FAN_RPM(channel); + args.arg1 =3D set ? *val : 0; + + err =3D lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id, + (unsigned char *)&args, sizeof(args), &retval); + if (err) + return err; + + if (!set) + *val =3D retval; + else if (retval !=3D 1) + return -EIO; + + return 0; +} + +/** + * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes + * @drvdata: Driver private data + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * + * Determines whether an HWMON attribute should be visible in sysfs + * based on hardware capabilities and current configuration. + * + * Return: permission mode, or 0 if invisible. + */ +static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_se= nsor_types type, + u32 attr, int channel) +{ + struct lwmi_om_priv *priv =3D (struct lwmi_om_priv *)drvdata; + bool visible =3D false; + + if (type =3D=3D hwmon_fan) { + if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID)) + return 0; + + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_target: + if (!(priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET)) + return 0; + + if (relax_fan_constraint || + (priv->fan_info[channel].min_rpm >=3D 0 && + priv->fan_info[channel].max_rpm >=3D 0)) + return 0644; + + /* + * Reaching here implies expose_all_fans is set. + * See lwmi_om_hwmon_add(). + */ + dev_warn_once(&priv->wdev->dev, + "fan tuning disabled due to missing RPM constraint\n"); + return 0; + case hwmon_fan_input: + visible =3D priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; + break; + case hwmon_fan_min: + visible =3D priv->fan_info[channel].min_rpm >=3D 0; + break; + case hwmon_fan_max: + visible =3D priv->fan_info[channel].max_rpm >=3D 0; + break; + } + } + + return visible ? 0444 : 0; +} + +/** + * lwmi_om_hwmon_read() - Read HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Pointer to store value + * + * Reads current sensor values from hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types = type, + u32 attr, int channel, long *val) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + u32 retval =3D 0; + int err; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_input: + err =3D lwmi_om_fan_get_set(priv, channel, &retval, false); + if (err) + return err; + + *val =3D retval; + return 0; + case hwmon_fan_enable: + *val =3D priv->fan_info[channel].last_target !=3D LWMI_FAN_STOP_RPM; + return 0; + case hwmon_fan_target: + if (priv->fan_info[channel].last_target =3D=3D LWMI_FAN_STOP_RPM) + return -EBUSY; + + *val =3D priv->fan_info[channel].last_target; + return 0; + case hwmon_fan_min: + *val =3D priv->fan_info[channel].min_rpm; + return 0; + case hwmon_fan_max: + *val =3D priv->fan_info[channel].max_rpm; + return 0; + } + } + + return -EOPNOTSUPP; +} + +/** + * lwmi_om_hwmon_write() - Write HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Value to write + * + * Writes configuration values to hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types= type, + u32 attr, int channel, long val) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + u32 raw, min_rpm, max_rpm; + int err; + + if (type =3D=3D hwmon_fan) { + switch (attr) { + case hwmon_fan_enable: + if (val =3D=3D 0) + raw =3D LWMI_FAN_STOP_RPM; + else if (val =3D=3D 1) + raw =3D 0; /* auto */ + else + return -EINVAL; + + goto fan_set; + case hwmon_fan_target: + if (val =3D=3D 0) { + raw =3D 0; + goto fan_set; + } + + min_rpm =3D relax_fan_constraint + ? LWMI_FAN_STOP_RPM + 1 + : priv->fan_info[channel].min_rpm; + max_rpm =3D relax_fan_constraint + ? U16_MAX + : priv->fan_info[channel].max_rpm; + + if (val < min_rpm || val > max_rpm) + return -EDOM; + + raw =3D val; +fan_set: + err =3D lwmi_om_fan_get_set(priv, channel, &raw, true); + if (err) + return err; + + priv->fan_info[channel].last_target =3D raw; + return 0; + } + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] =3D { + /* Must match LWMI_FAN_NR. */ + HWMON_CHANNEL_INFO(fan, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET | + HWMON_F_MIN | HWMON_F_MAX), + NULL }; =20 +static const struct hwmon_ops lwmi_om_hwmon_ops =3D { + .is_visible =3D lwmi_om_hwmon_is_visible, + .read =3D lwmi_om_hwmon_read, + .write =3D lwmi_om_hwmon_write, +}; + +static const struct hwmon_chip_info lwmi_om_hwmon_chip_info =3D { + .ops =3D &lwmi_om_hwmon_ops, + .info =3D lwmi_om_hwmon_info, +}; + +/** + * lwmi_om_hwmon_add() - Register HWMON device if all info is collected + * @priv: Driver private data + */ +static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) +{ + int i, valid; + + if (WARN_ON(priv->hwmon_dev)) + return; + + if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_= collected) { + dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\= n", + priv->fan_flags.capdata00_collected, + priv->fan_flags.capdata_fan_collected); + return; + } + + if (expose_all_fans) + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); + + if (relax_fan_constraint) + dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution= \n"); + + valid =3D 0; + for (i =3D 0; i < LWMI_FAN_NR; i++) { + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) + continue; + + valid++; + + if (!expose_all_fans && + (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) { + dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n", + LWMI_FAN_ID(i)); + priv->fan_info[i].supported =3D 0; + valid--; + } + } + + if (valid =3D=3D 0) { + dev_warn(&priv->wdev->dev, + "fan reporting/tuning is unsupported on this device\n"); + return; + } + + priv->hwmon_dev =3D hwmon_device_register_with_info(&priv->wdev->dev, + LWMI_OM_HWMON_NAME, priv, + &lwmi_om_hwmon_chip_info, + NULL); + if (IS_ERR(priv->hwmon_dev)) { + dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n", + PTR_ERR(priv->hwmon_dev)); + priv->hwmon_dev =3D NULL; + return; + } + + dev_dbg(&priv->wdev->dev, "registered HWMON device\n"); +} + +/** + * lwmi_om_hwmon_remove() - Unregister HWMON device + * @priv: Driver private data + * + * Unregisters the HWMON device if applicable. + */ +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) +{ + if (!priv->hwmon_dev) + return; + + hwmon_device_unregister(priv->hwmon_dev); + priv->hwmon_dev =3D NULL; +} + +/** + * lwmi_om_fan_info_init() - Initialzie fan info + * @priv: Driver private data + * + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() m= ay be + * called in an arbitrary order. Hence, initializion must be done before. + */ +static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv) +{ + int i; + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + priv->fan_info[i] =3D (struct fan_info) { + .supported =3D 0, + /* + * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot. + * + * Note that S0ix (s2idle) preserves the RPM target, so we don't need + * suspend/resume callbacks. This behavior has not been tested on S3- + * capable devices, but I doubt if such devices even have this interfac= e. + */ + .last_target =3D 0, + .min_rpm =3D -ENODATA, + .max_rpm =3D -ENODATA, + }; + } + + priv->fan_flags.capdata00_collected =3D false; + priv->fan_flags.capdata_fan_collected =3D false; +} + +/** + * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00 + * @priv: Driver private data + */ +static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv) +{ + struct capdata00 capdata00; + int i, err; + + dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n"); + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + err =3D lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &ca= pdata00); + priv->fan_info[i].supported =3D err ? 0 : capdata00.supported; + } + + priv->fan_flags.capdata00_collected =3D true; + lwmi_om_hwmon_add(priv); +} + +/** + * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan + * @dev: Pointer to the lenovo-wmi-other device + * @cd_fan_list: Pointer to the capdata fan list + */ +static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_= list *cd_fan_list) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + struct capdata_fan capdata_fan; + int i, err; + + dev_dbg(dev, "Collecting fan info from capdata_fan\n"); + + if (!cd_fan_list) + goto out; + + for (i =3D 0; i < LWMI_FAN_NR; i++) { + err =3D lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan); + if (err) + continue; + + priv->fan_info[i].min_rpm =3D capdata_fan.min_rpm; + priv->fan_info[i].max_rpm =3D capdata_fan.max_rpm; + } +out: + priv->fan_flags.capdata_fan_collected =3D true; + lwmi_om_hwmon_add(priv); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D fw_attributes (component: lenovo-wmi-capdata 0= 1) =3D=3D=3D=3D=3D=3D=3D=3D */ + struct tunable_attr_01 { struct capdata01 *capdata; struct device *dev; @@ -559,32 +1007,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_pr= iv *priv) device_unregister(priv->fw_attr_dev); } =20 +/* =3D=3D=3D=3D=3D=3D=3D=3D Self (master: lenovo-wmi-other) =3D=3D=3D=3D= =3D=3D=3D=3D */ + /** * lwmi_om_master_bind() - Bind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device. * - * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the - * lenovo-wmi-other master driver. On success, assign the capability data = 01 - * list pointer to the driver data struct for later access. This pointer - * is only valid while the capdata01 interface exists. Finally, register a= ll - * firmware attribute groups. + * Call component_bind_all to bind the lenovo-wmi-capdata devices to the + * lenovo-wmi-other master driver, with a callback to collect fan info from + * capdata_fan. On success, assign the capability data list pointers to the + * driver data struct for later access. These pointers are only valid whil= e the + * capdata interfaces exist. Finally, collect fan info from capdata00 and + * register all firmware attribute groups. Note that the HWMON device is + * registered only if all fan info is collected. Hence, it is not register= ed + * here. See lwmi_om_fan_info_collect_cd00() and + * lwmi_om_fan_info_collect_cd_fan(). * * Return: 0 on success, or an error code. */ static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); - struct lwmi_cd_binder binder =3D { 0 }; + struct lwmi_cd_binder binder =3D { + .cd_fan_list_cb =3D lwmi_om_fan_info_collect_cd_fan, + }; int ret; =20 + lwmi_om_fan_info_init(priv); + ret =3D component_bind_all(dev, &binder); if (ret) return ret; =20 + priv->cd00_list =3D binder.cd00_list; priv->cd01_list =3D binder.cd01_list; - if (!priv->cd01_list) + if (!priv->cd00_list || !priv->cd01_list) return -ENODEV; =20 + lwmi_om_fan_info_collect_cd00(priv); + return lwmi_om_fw_attr_add(priv); } =20 @@ -592,15 +1053,18 @@ static int lwmi_om_master_bind(struct device *dev) * lwmi_om_master_unbind() - Unbind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device * - * Unregister all capability data attribute groups. Then call - * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the - * lenovo-wmi-other master driver. Finally, free the IDA for this device. + * Unregister all firmware attribute groups and the HWMON device. Then call + * component_unbind_all to unbind lenovo-wmi-capdata devices from the + * lenovo-wmi-other master driver. */ static void lwmi_om_master_unbind(struct device *dev) { struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); =20 lwmi_om_fw_attr_remove(priv); + + lwmi_om_hwmon_remove(priv); + component_unbind_all(dev, NULL); } =20 @@ -665,5 +1129,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); MODULE_LICENSE("GPL"); --=20 2.51.0