* [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
@ 2025-11-25 19:49 Rong Zhang
2025-11-25 19:49 ` [PATCH v7 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
Lenovo WMI Other Mode interface also supports querying or setting fan
speed RPM. This capability is described by LENOVO_CAPABILITY_DATA_00.
Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
cooling fans, including minimum and maximum fan speed RPM.
This patchset turns lenovo-wmi-capdata01 into a unified driver (now
named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
- fanX_enable: enable/disable the fan (tunable)
- fanX_input: current RPM
- fanX_max: maximum RPM
- fanX_min: minimum RPM
- fanX_target: target RPM (tunable)
LENOVO_CAPABILITY_DATA_{00,01} presents on all devices, so
both binds to lenovo-wmi-other. However, some device does not have
LENOVO_FAN_TEST_DATA and its presence is described by
LENOVO_CAPABILITY_DATA_00; hence, the former binds to the latter and a
callback is used to pass the data to lenovo-wmi-other.
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 presence of WMI GUIDs
- The notifier framework cannot cleanly connect capdata_fan to
lenovo-wmi-other without introducing assumptions on probing sequence
capdata00 is registered as a component and a sub-master on probe,
instead of chaining the registrations 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.
The implementation does not rely on a specific binding sequence. This
has been fuzz-tested using:
#!/bin/bash
DRV_DIR=/sys/bus/wmi/drivers/lenovo_wmi_cd
CAPDATA_GUIDS=(
$(find "$DRV_DIR"/ -name '*-*-*-*-*-*' -printf "%f ")
)
b() { sudo tee "$DRV_DIR"/bind <<<"$1"; }
u() { sudo tee "$DRV_DIR"/unbind <<<"$1"; }
for guid in "${CAPDATA_GUIDS[@]}"; do
u "$guid"
done
while read -rsa perm; do
for guid in "${perm[@]}"; do
b "$guid"
done
sensors | grep -A3 lenovo_wmi_other || true
for guid in "${perm[@]}"; do
u "$guid"
done
done < <(python3 -c "
from itertools import permutations
ps = permutations('${CAPDATA_GUIDS[*]}'.split())
for p in ps: print(' '.join(p))")
for guid in "${CAPDATA_GUIDS[@]}"; do
b "$guid"
done
Tested on ThinkBook 14 G7+ ASP.
Changes in v7:
- Fix missing #include (thanks Ilpo Järvinen)
- Fix formatting issues (ditto)
- dev_dbg() instead of dev_info() on probe success (ditto)
- Rearrange to drop some gotos (ditto)
- Move the declarations of __free()-managed variables to where they are
assigned (ditto)
- Improve the readability of struct definition and error paths (ditto)
- Prevent back-and-forth changes (ditto)
- Fix mistakenly inverted boundary check
- Emit unaligned access to Fan Test Data's WMI block
- Properly calculate array index when we truncate Fan Test Data
- Fix typo
- Link to v6: https://lore.kernel.org/r/20251122184522.18677-1-i@rong.moe/
Changes in v6:
- Fix mistaken error paths
- Link to v5: https://lore.kernel.org/r/20251114175927.52533-1-i@rong.moe/
Changes in v5:
- Do not cast pointer to non-pointer or vice versa (thanks kernel test
robot)
- Fix missing include (ditto)
- Link to v4: https://lore.kernel.org/r/20251113191152.96076-1-i@rong.moe/
Changes in v4:
- Get rid of wmi_has_guid() (thanks Armin Wolf's inspiration)
- Add [PATCH v4 6/7], please review & test
- Check 0x04050000.supported and bind capdata_fan to capdata00
- Rework HWMON registration
- 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 presence of
capdata_fan
- Drop Reviewed-by & Tested-by from [PATCH v4 7/7] due to the changes,
please review & test
- Link to v3: https://lore.kernel.org/r/20251031155349.24693-1-i@rong.moe/
Changes in v3:
- Fix grammar (thanks Derek J. Clark)
- Link to v2: https://lore.kernel.org/r/20251030193955.107148-1-i@rong.moe/
Changes in v2:
- Add a workaround for ACPI methods that return a 4B buffer for u32
(thanks Armin Wolf)
- Fix function documentation (thanks kernel test bot)
- Reword documentation (thanks Derek J. Clark)
- Squash min/max reporting patch into the initial HWMON 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)
- Do not register HWMON device if no fan can be exposed
- fanX_target: Return -EBUSY instead of raw target value when fan stops
- Link to v1: https://lore.kernel.org/r/20251019210450.88830-1-i@rong.moe/
Rong Zhang (7):
platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32
platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
Data
platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning
.../wmi/devices/lenovo-wmi-other.rst | 43 +-
drivers/platform/x86/lenovo/Kconfig | 5 +-
drivers/platform/x86/lenovo/Makefile | 2 +-
drivers/platform/x86/lenovo/wmi-capdata.c | 812 ++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 65 ++
drivers/platform/x86/lenovo/wmi-capdata01.c | 302 -------
drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
drivers/platform/x86/lenovo/wmi-helpers.c | 21 +-
drivers/platform/x86/lenovo/wmi-other.c | 515 ++++++++++-
9 files changed, 1433 insertions(+), 357 deletions(-)
create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
base-commit: ac3fd01e4c1efce8f2c054cdeb2ddd2fc0fb150d
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 >=4B buffer into u32 to support these
devices.
Suggested-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/f1787927-b655-4321-b9d9-bc12353c72db@gmx.de/
Signed-off-by: Rong Zhang <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
---
Changes in v7:
- Fix mistakenly inverted boundary check
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 | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index f6fef6296251..7379defac500 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -21,6 +21,7 @@
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
+#include <linux/unaligned.h>
#include <linux/wmi.h>
#include "wmi-helpers.h"
@@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
if (!ret_obj)
return -ENODATA;
- if (ret_obj->type != 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 < sizeof(u32))
+ return -ENXIO;
- *retval = (u32)ret_obj->integer.value;
+ *retval = get_unaligned_le32(ret_obj->buffer.pointer);
+ return 0;
+ case ACPI_TYPE_INTEGER:
+ *retval = (u32)ret_obj->integer.value;
+ return 0;
+ default:
+ return -ENXIO;
+ }
}
return 0;
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-25 19:49 ` [PATCH v7 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
Prepare for the upcoming changes to make it suitable to retrieve
and provide other Capability Data as well.
Signed-off-by: Rong Zhang <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/lenovo/Kconfig | 4 +-
drivers/platform/x86/lenovo/Makefile | 2 +-
.../lenovo/{wmi-capdata01.c => wmi-capdata.c} | 124 +++++++++---------
.../lenovo/{wmi-capdata01.h => 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 => wmi-capdata.c} (60%)
rename drivers/platform/x86/lenovo/{wmi-capdata01.h => wmi-capdata.h} (60%)
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/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.
-config LENOVO_WMI_DATA01
+config LENOVO_WMI_DATA
tristate
depends on ACPI_WMI
@@ -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/lenovo/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) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
-lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o
+lenovo-target-$(CONFIG_LENOVO_WMI_DATA) += wmi-capdata.o
lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += 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 used 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 thermal
- * 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 hardware,
+ * 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 <derekjohn.clark@gmail.com>
+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
*/
#include <linux/acpi.h>
@@ -26,55 +29,55 @@
#include <linux/types.h>
#include <linux/wmi.h>
-#include "wmi-capdata01.h"
+#include "wmi-capdata.h"
#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_NOTIFY_STATUS 0x80
-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;
};
-struct cd01_list {
+struct cd_list {
struct mutex list_mutex; /* list R/W mutex */
u8 count;
struct capdata01 data[];
};
/**
- * 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 capdata01
- * 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 capdata
+ * 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 = dev_get_drvdata(cd01_dev);
- struct cd01_list **cd01_list = data;
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+ struct cd_list **cd_list = data;
- *cd01_list = priv->list;
+ *cd_list = priv->list;
return 0;
}
-static const struct component_ops lwmi_cd01_component_ops = {
- .bind = lwmi_cd01_component_bind,
+static const struct component_ops lwmi_cd_component_ops = {
+ .bind = lwmi_cd_component_bind,
};
/**
* 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 = {
*
* Return: 0 on success, or -EINVAL.
*/
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output)
{
u8 idx;
@@ -97,17 +100,17 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata0
return -EINVAL;
}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
/**
- * 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;
@@ -131,17 +134,17 @@ static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
}
/**
- * 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 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_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;
@@ -163,28 +166,28 @@ static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
}
/**
- * 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 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
* 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;
- ret = lwmi_cd01_alloc(priv);
+ ret = lwmi_cd_alloc(priv);
if (ret)
return ret;
- return lwmi_cd01_cache(priv);
+ return lwmi_cd_cache(priv);
}
/**
- * 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_block *nb, unsigned long acti
void *data)
{
struct acpi_bus_event *event = data;
- struct lwmi_cd01_priv *priv;
+ struct lwmi_cd_priv *priv;
int ret;
if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
return NOTIFY_DONE;
- priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
+ priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
switch (event->type) {
case ACPI_AC_NOTIFY_STATUS:
- ret = lwmi_cd01_cache(priv);
+ ret = lwmi_cd_cache(priv);
if (ret)
return NOTIFY_BAD;
@@ -230,10 +233,9 @@ static void lwmi_cd01_unregister(void *data)
unregister_acpi_notifier(acpi_nb);
}
-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;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
@@ -243,7 +245,7 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- ret = lwmi_cd01_setup(priv);
+ ret = lwmi_cd_setup(priv);
if (ret)
return ret;
@@ -257,27 +259,27 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
if (ret)
return ret;
- return component_add(&wdev->dev, &lwmi_cd01_component_ops);
+ return component_add(&wdev->dev, &lwmi_cd_component_ops);
}
-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);
}
-static const struct wmi_device_id lwmi_cd01_id_table[] = {
+static const struct wmi_device_id lwmi_cd_id_table[] = {
{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
{}
};
-static struct wmi_driver lwmi_cd01_driver = {
+static struct wmi_driver lwmi_cd_driver = {
.driver = {
- .name = "lenovo_wmi_cd01",
+ .name = "lenovo_wmi_cd",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
- .id_table = lwmi_cd01_id_table,
- .probe = lwmi_cd01_probe,
- .remove = lwmi_cd01_remove,
+ .id_table = lwmi_cd_id_table,
+ .probe = lwmi_cd_probe,
+ .remove = lwmi_cd_remove,
.no_singleton = true,
};
@@ -290,13 +292,13 @@ static struct wmi_driver lwmi_cd01_driver = {
*/
int lwmi_cd01_match(struct device *dev, void *data)
{
- return dev->driver == &lwmi_cd01_driver.driver;
+ return dev->driver == &lwmi_cd_driver.driver;
}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD");
-module_wmi_driver(lwmi_cd01_driver);
+module_wmi_driver(lwmi_cd_driver);
-MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
+MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
-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 @@
/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
-#ifndef _LENOVO_WMI_CAPDATA01_H_
-#define _LENOVO_WMI_CAPDATA01_H_
+#ifndef _LENOVO_WMI_CAPDATA_H_
+#define _LENOVO_WMI_CAPDATA_H_
#include <linux/types.h>
struct device;
-struct cd01_list;
+struct cd_list;
struct capdata01 {
u32 id;
@@ -19,7 +19,7 @@ struct capdata01 {
u32 max_value;
};
-int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
int lwmi_cd01_match(struct device *dev, void *data);
-#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 <linux/types.h>
#include <linux/wmi.h>
-#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 {
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 = dev_get_drvdata(dev);
- struct cd01_list *tmp_list;
+ struct cd_list *tmp_list;
int ret;
ret = component_bind_all(dev, &tmp_list);
@@ -657,7 +660,7 @@ static struct wmi_driver lwmi_other_driver = {
module_wmi_driver(lwmi_other_driver);
-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 <derekjohn.clark@gmail.com>");
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-25 19:49 ` [PATCH v7 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
2025-11-25 19:49 ` [PATCH v7 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
Changes in v7:
- Fix missing #include (thanks Ilpo Järvinen)
- Fix formatting issues (ditto)
- dev_dbg() instead of dev_info() on probe success (ditto)
Changes in v6:
- Fix the error path of lwmi_cd_match_add_all()
- IS_ERR(matchptr) => 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 | 231 +++++++++++++++++-----
drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
drivers/platform/x86/lenovo/wmi-other.c | 16 +-
3 files changed, 195 insertions(+), 59 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index c5e74b2bfeb3..1b80c51952d2 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -12,13 +12,21 @@
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
* - Initial implementation (formerly named lenovo-wmi-capdata01)
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - Unified implementation
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/acpi.h>
+#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/module.h>
@@ -26,6 +34,7 @@
#include <linux/mutex_types.h>
#include <linux/notifier.h>
#include <linux/overflow.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/wmi.h>
@@ -36,6 +45,23 @@
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_NOTIFY_STATUS 0x80
+enum lwmi_cd_type {
+ LENOVO_CAPABILITY_DATA_01,
+};
+
+#define LWMI_CD_TABLE_ITEM(_type) \
+ [_type] = { \
+ .name = #_type, \
+ .type = _type, \
+ }
+
+static const struct lwmi_cd_info {
+ const char *name;
+ enum lwmi_cd_type type;
+} lwmi_cd_table[] = {
+ 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 +70,63 @@ struct lwmi_cd_priv {
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);
+ };
};
+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 != &lwmi_cd_driver.driver)
+ return false;
+
+ priv = dev_get_drvdata(dev);
+ return priv->list->type == *(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 @master
+ * 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 = 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 capdata
* list. This is used to call lwmi_cd*_get_data to look up attribute data
@@ -64,9 +138,15 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
struct device *om_dev, void *data)
{
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
- struct cd_list **cd_list = data;
+ struct lwmi_cd_binder *binder = data;
- *cd_list = priv->list;
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ binder->cd01_list = priv->list;
+ break;
+ default:
+ return -EINVAL;
+ }
return 0;
}
@@ -76,30 +156,35 @@ static const struct component_ops lwmi_cd_component_ops = {
};
/**
- * 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 capdata01 *output)
-{
- u8 idx;
-
- guard(mutex)(&list->list_mutex);
- for (idx = 0; idx < list->count; idx++) {
- if (list->data[idx].id != 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, _output_t *output) \
+ { \
+ u8 idx; \
+ \
+ if (WARN_ON(list->type != _cd_type)) \
+ return -EINVAL; \
+ \
+ guard(mutex)(&list->list_mutex); \
+ for (idx = 0; idx < list->count; idx++) { \
+ if (list->_cdxx[idx].id != attribute_id) \
+ continue; \
+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
+ return 0; \
+ } \
+ return -EINVAL; \
}
- 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");
/**
@@ -112,10 +197,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 = &priv->list->cd01[0];
+ size = sizeof(priv->list->cd01[0]);
+ break;
+ default:
+ return -EINVAL;
+ }
guard(mutex)(&priv->list->list_mutex);
- for (idx = 0; idx < priv->list->count; idx++) {
+ for (idx = 0; idx < priv->list->count; idx++, p += size) {
union acpi_object *ret_obj __free(kfree) = NULL;
ret_obj = wmidev_block_query(priv->wdev, idx);
@@ -123,11 +219,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
return -ENODEV;
if (ret_obj->type != ACPI_TYPE_BUFFER ||
- ret_obj->buffer.length < sizeof(priv->list->data[idx]))
+ ret_obj->buffer.length < size)
continue;
- memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
- ret_obj->buffer.length);
+ memcpy(p, ret_obj->buffer.pointer, size);
}
return 0;
@@ -136,20 +231,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;
count = wmidev_instance_count(priv->wdev);
- list_size = struct_size(list, data, count);
+
+ switch (type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ list_size = struct_size(list, cd01, count);
+ break;
+ default:
+ return -EINVAL;
+ }
list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
if (!list)
@@ -159,6 +262,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
if (ret)
return ret;
+ list->type = type;
list->count = count;
priv->list = list;
@@ -168,6 +272,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 +280,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;
- ret = lwmi_cd_alloc(priv);
+ ret = lwmi_cd_alloc(priv, type);
if (ret)
return ret;
@@ -235,9 +340,13 @@ static void lwmi_cd01_unregister(void *data)
static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
{
+ const struct lwmi_cd_info *info = context;
struct lwmi_cd_priv *priv;
int ret;
+ if (!info)
+ return -EINVAL;
+
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -245,30 +354,58 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- ret = lwmi_cd_setup(priv);
+ ret = lwmi_cd_setup(priv, info->type);
if (ret)
- return ret;
+ goto out;
- priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+ switch (info->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
- ret = register_acpi_notifier(&priv->acpi_nb);
- if (ret)
- return ret;
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ goto out;
- ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
- if (ret)
- return ret;
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
- return component_add(&wdev->dev, &lwmi_cd_component_ops);
+ ret = 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_dbg(&wdev->dev, "registered %s with %u items\n",
+ info->name, priv->list->count);
+ }
+ return ret;
}
static void lwmi_cd_remove(struct wmi_device *wdev)
{
- component_del(&wdev->dev, &lwmi_cd_component_ops);
+ struct lwmi_cd_priv *priv = 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);
+ }
}
+#define LWMI_CD_WDEV_ID(_type) \
+ .guid_string = _type##_GUID, \
+ .context = &lwmi_cd_table[_type],
+
static const struct wmi_device_id lwmi_cd_id_table[] = {
- { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
{}
};
@@ -283,22 +420,10 @@ static struct wmi_driver lwmi_cd_driver = {
.no_singleton = true,
};
-/**
- * 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 == &lwmi_cd_driver.driver;
-}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD");
-
module_wmi_driver(lwmi_cd_driver);
MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/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 @@
#include <linux/types.h>
+struct component_match;
struct device;
struct cd_list;
@@ -19,7 +20,11 @@ struct capdata01 {
u32 max_value;
};
+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 capdata01 *output);
-int lwmi_cd01_match(struct device *dev, void *data);
#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..a2325b5ec608 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_priv *priv)
static int lwmi_om_master_bind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
- struct cd_list *tmp_list;
+ struct lwmi_cd_binder binder = {};
int ret;
- ret = component_bind_all(dev, &tmp_list);
+ ret = component_bind_all(dev, &binder);
if (ret)
return ret;
- priv->cd01_list = tmp_list;
+ priv->cd01_list = binder.cd01_list;
if (!priv->cd01_list)
return -ENODEV;
@@ -623,10 +623,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
if (!priv)
return -ENOMEM;
+ /* Sentinel for on-demand ida_free(). */
+ priv->ida_id = -EIDRM;
+
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- 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);
@@ -639,7 +642,10 @@ static void lwmi_other_remove(struct wmi_device *wdev)
struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
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 >= 0)
+ ida_free(&lwmi_om_ida, priv->ida_id);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (2 preceding siblings ...)
2025-11-25 19:49 ` [PATCH v7 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
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:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+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
-------------------------
WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
-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.
Each attribute has the following properties:
- current_value
@@ -48,7 +57,7 @@ Each attribute has the following properties:
- scalar_increment
- type
-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/x86/lenovo/wmi-capdata.c
index 1b80c51952d2..08a78dc490f6 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 hardware,
+ * and the default_value. All attributes are independent of thermal modes.
+ *
* Capability Data 01 includes if the attribute is supported by the hardware,
* 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
@@ -40,12 +43,14 @@
#include "wmi-capdata.h"
+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_NOTIFY_STATUS 0x80
enum lwmi_cd_type {
+ LENOVO_CAPABILITY_DATA_00,
LENOVO_CAPABILITY_DATA_01,
};
@@ -59,6 +64,7 @@ static const struct lwmi_cd_info {
const char *name;
enum lwmi_cd_type type;
} lwmi_cd_table[] = {
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
};
@@ -74,6 +80,7 @@ struct cd_list {
u8 count;
union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
DECLARE_FLEX_ARRAY(struct capdata01, cd01);
};
};
@@ -141,6 +148,9 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
struct lwmi_cd_binder *binder = data;
switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ binder->cd00_list = priv->list;
+ break;
case LENOVO_CAPABILITY_DATA_01:
binder->cd01_list = priv->list;
break;
@@ -184,6 +194,9 @@ static const struct component_ops lwmi_cd_component_ops = {
return -EINVAL; \
}
+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");
@@ -202,6 +215,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
void *p;
switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ p = &priv->list->cd00[0];
+ size = sizeof(priv->list->cd00[0]);
+ break;
case LENOVO_CAPABILITY_DATA_01:
p = &priv->list->cd01[0];
size = sizeof(priv->list->cd01[0]);
@@ -247,6 +264,9 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
count = wmidev_instance_count(priv->wdev);
switch (type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ list_size = struct_size(list, cd00, count);
+ break;
case LENOVO_CAPABILITY_DATA_01:
list_size = struct_size(list, cd01, count);
break;
@@ -359,6 +379,9 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
goto out;
switch (info->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ goto out;
case LENOVO_CAPABILITY_DATA_01:
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
@@ -392,6 +415,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
case LENOVO_CAPABILITY_DATA_01:
component_del(&wdev->dev, &lwmi_cd_component_ops);
break;
@@ -405,6 +429,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
.context = &lwmi_cd_table[_type],
static const struct wmi_device_id lwmi_cd_id_table[] = {
+ { 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/x86/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;
+struct capdata00 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+};
+
struct capdata01 {
u32 id;
u32 supported;
@@ -21,10 +27,12 @@ struct capdata01 {
};
struct lwmi_cd_binder {
+ struct cd_list *cd00_list;
struct cd_list *cd01_list;
};
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 capdata00 *output);
int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
#endif /* !_LENOVO_WMI_CAPDATA_H_ */
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (3 preceding siblings ...)
2025-11-25 19:49 ` [PATCH v7 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
Changes in v7:
- Rearrange lwmi_cd_fan_list_alloc_cache() to drop gotos (thanks Ilpo
Järvinen)
- Move the declarations of __free()-managed variablesto where thet are
assigned (ditto)
- Prevent back-and-forth changes (ditto)
- Improve the readablity of struct definition (ditto)
- Emit unaligned access to the WMI block
- Properly calculate array index when we truncate the data
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 | 96 +++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 7 ++
3 files changed, 120 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
+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.
WMI interface description
=========================
@@ -115,3 +122,13 @@ data using the `bmfdec <https://github.com/pali/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("Definition 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")] uint32 FanId[];
+ [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
+ [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
+ };
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 08a78dc490f6..9fed162abd17 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 capable
+ * to spin faster than max RPM or slower than min RPM.
+ *
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
* - Initial implementation (formerly named lenovo-wmi-capdata01)
*
@@ -45,6 +49,7 @@
#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_NOTIFY_STATUS 0x80
@@ -52,6 +57,7 @@
enum lwmi_cd_type {
LENOVO_CAPABILITY_DATA_00,
LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
};
#define LWMI_CD_TABLE_ITEM(_type) \
@@ -66,6 +72,7 @@ static const struct lwmi_cd_info {
} lwmi_cd_table[] = {
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
+ LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
};
struct lwmi_cd_priv {
@@ -82,6 +89,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);
};
};
@@ -121,6 +129,10 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match
return;
for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ /* Skip sub-components. */
+ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
+ continue;
+
component_match_add(master, matchptr, lwmi_cd_match,
(void *)&lwmi_cd_table[i].type);
if (IS_ERR(*matchptr))
@@ -200,6 +212,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");
+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.
@@ -223,6 +238,9 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
p = &priv->list->cd01[0];
size = sizeof(priv->list->cd01[0]);
break;
+ case LENOVO_FAN_TEST_DATA:
+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
+ return 0;
default:
return -EINVAL;
}
@@ -245,6 +263,72 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
return 0;
}
+/**
+ * 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)
+{
+ struct cd_list *list;
+ size_t size;
+ u32 count;
+ int idx;
+
+ /* Emit unaligned access to u8 buffer with __packed. */
+ struct cd_fan_block {
+ u32 nr;
+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
+ } __packed * block;
+
+ union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type == ACPI_TYPE_BUFFER) {
+ block = (struct cd_fan_block *)ret_obj->buffer.pointer;
+ size = ret_obj->buffer.length;
+
+ count = size >= 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 = 0;
+ } else if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+ } else {
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in sub-master device being
+ * unbound. This behavior aligns with lwmi_cd_cache().
+ */
+ count = 0;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ /* Do not calculate array index using count, as it may be truncated. */
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = block->data[idx],
+ .max_rpm = block->data[idx + block->nr],
+ .min_rpm = block->data[idx + (2 * block->nr)],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
/**
* lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
* @priv: lenovo-wmi-capdata driver data.
@@ -270,6 +354,12 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
case LENOVO_CAPABILITY_DATA_01:
list_size = struct_size(list, cd01, count);
break;
+ case LENOVO_FAN_TEST_DATA:
+ count = lwmi_cd_fan_list_alloc_cache(priv, &list);
+ if (count < 0)
+ return count;
+
+ goto got_list;
default:
return -EINVAL;
}
@@ -278,6 +368,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
if (!list)
return -ENOMEM;
+got_list:
ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
if (ret)
return ret;
@@ -396,6 +487,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
goto out;
+ case LENOVO_FAN_TEST_DATA:
+ goto out;
default:
return -EINVAL;
}
@@ -419,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);
}
@@ -431,6 +526,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
static const struct wmi_device_id lwmi_cd_id_table[] = {
{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
+ { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
{}
};
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/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;
};
+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 capdata00 *output);
int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
#endif /* !_LENOVO_WMI_CAPDATA_H_ */
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (4 preceding siblings ...)
2025-11-25 19:49 ` [PATCH v7 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 19:49 ` [PATCH v7 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Rong Zhang
2025-11-25 21:43 ` [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 presence of WMI GUIDs
- The notifier framework cannot cleanly connect capdata_fan to
lenovo-wmi-other without introducing assumptions on probing sequence
capdata00 is registered as a component and a sub-master on probe,
instead of chaining the registrations 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 <i@rong.moe>
---
Changes in v7:
- Improve the readability of error paths (thanks Ilpo Järvinen)
- Prevent back-and-forth changes (ditto)
- Fix typo
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 | 264 +++++++++++++++++++++-
drivers/platform/x86/lenovo/wmi-capdata.h | 20 ++
drivers/platform/x86/lenovo/wmi-other.c | 5 -
3 files changed, 283 insertions(+), 6 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 9fed162abd17..555f9446526c 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
#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/component.h>
@@ -54,10 +55,17 @@
#define ACPI_AC_CLASS "ac_adapter"
#define ACPI_AC_NOTIFY_STATUS 0x80
+#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 = -1,
};
#define LWMI_CD_TABLE_ITEM(_type) \
@@ -79,6 +87,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-component. */
+ bool registered; /* Has the sub-master been registered? */
+ } *sub_master;
};
struct cd_list {
@@ -141,6 +163,56 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match
}
EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD");
+/**
+ * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
+ * @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 = priv->sub_master->sub_component_list;
+
+ /*
+ * 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_list
+ *
+ * If a binding has been unbound before the other binding is bound, the
+ * corresponding members filled by the former are guaranteed to be cleared.
+ *
+ * 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) == -ENODEV)
+ sub_component_list = 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 = NULL;
+ priv->sub_master->master_dev = NULL;
+ priv->sub_master->sub_component_list = NULL;
+}
+
/**
* lwmi_cd_component_bind() - Bind component to master device.
* @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
@@ -151,6 +223,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,
@@ -162,6 +236,11 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
switch (priv->list->type) {
case LENOVO_CAPABILITY_DATA_00:
binder->cd00_list = priv->list;
+
+ priv->sub_master->master_dev = om_dev;
+ priv->sub_master->master_cb = binder->cd_fan_list_cb;
+ lwmi_cd_call_master_cb(priv);
+
break;
case LENOVO_CAPABILITY_DATA_01:
binder->cd01_list = priv->list;
@@ -173,8 +252,168 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
return 0;
}
+/**
+ * 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 device 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 = dev_get_drvdata(cd_dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ priv->sub_master->master_dev = NULL;
+ priv->sub_master->master_cb = NULL;
+ return;
+ default:
+ return;
+ }
+}
+
static const struct component_ops lwmi_cd_component_ops = {
.bind = lwmi_cd_component_bind,
+ .unbind = 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-master
+ * device. On success, collect the pointer to the sub-component list and try
+ * 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 = dev_get_drvdata(dev);
+ struct cd_list *sub_component_list;
+ int ret;
+
+ ret = component_bind_all(dev, &sub_component_list);
+ if (ret)
+ return ret;
+
+ priv->sub_master->sub_component_list = 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 binding
+ * establishment between the sub-master and the sub-component (if it's about to
+ * happen) from calling the master callback. Then, call component_unbind_all 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 = dev_get_drvdata(dev);
+
+ priv->sub_master->sub_component_list = NULL;
+
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_cd_sub_master_ops = {
+ .bind = lwmi_cd_sub_master_bind,
+ .unbind = 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 sub-
+ * 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 = NULL;
+ int ret;
+
+ priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
+ if (!priv->sub_master)
+ return -ENOMEM;
+
+ if (sub_component_type == CD_TYPE_NONE) {
+ /* The master callback will be called with NULL on bind. */
+ priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
+ priv->sub_master->registered = 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 = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
+ master_match);
+ if (ret)
+ return ret;
+
+ priv->sub_master->registered = 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)
+ return;
+
+ component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
+ priv->sub_master->registered = 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 = dev_get_drvdata(sc_dev);
+ struct cd_list **listp = data;
+
+ *listp = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd_sub_component_ops = {
+ .bind = lwmi_cd_sub_component_bind,
};
/**
@@ -470,9 +709,28 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
goto out;
switch (info->type) {
- case LENOVO_CAPABILITY_DATA_00:
+ case LENOVO_CAPABILITY_DATA_00: {
+ enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
+ struct capdata00 capdata00;
+
+ ret = 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 = CD_TYPE_NONE;
+ }
+
+ /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
+ ret = lwmi_cd_sub_master_add(priv, sub_component_type);
+ if (ret)
+ goto out;
+
+ /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
ret = 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 = lwmi_cd01_notifier_call;
@@ -488,6 +746,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
goto out;
case LENOVO_FAN_TEST_DATA:
+ ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
goto out;
default:
return -EINVAL;
@@ -509,10 +768,13 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
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/x86/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_
+#include <linux/bits.h>
#include <linux/types.h>
+#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;
};
+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;
};
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 a2325b5ec608..c1cf2278369e 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
-#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"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (5 preceding siblings ...)
2025-11-25 19:49 ` [PATCH v7 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
@ 2025-11-25 19:49 ` Rong Zhang
2025-11-25 21:43 ` [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
7 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-25 19:49 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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 <i@rong.moe>
---
Changes in v7:
- Fix formatting issues (thanks Ilpo Järvinen)
- Improve the readability of struct lwmi_fan_info's definition (ditto)
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 | 489 +++++++++++++++++-
3 files changed, 491 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:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+Additionally, this driver also exports attributes to HWMON.
+
LENOVO_CAPABILITY_DATA_00
-------------------------
@@ -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.
+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
-------------------------
@@ -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.
+The following HWMON attributes are implemented:
+ - fanX_max: maximum RPM
+ - fanX_min: minimum RPM
+
WMI interface description
=========================
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/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 c1cf2278369e..e54343b10f2e 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 <derekjohn.clark@gmail.com>
+ * - fw_attributes
+ * - binding to Capability Data 01
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - HWMON
+ * - binding to Capability Data 00 and Fan
*/
#include <linux/acpi.h>
@@ -25,6 +34,7 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
@@ -49,12 +59,26 @@
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
+#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"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
static DEFINE_IDA(lwmi_om_ida);
@@ -67,19 +91,447 @@ enum attribute_property {
SUPPORTED,
};
+struct lwmi_fan_info {
+ u32 supported;
+ u32 last_target;
+ long min_rpm;
+ long max_rpm;
+};
+
struct lwmi_om_priv {
struct component_master_ops *ops;
/* only valid after capdata bind */
+ struct cd_list *cd00_list;
struct cd_list *cd01_list;
+ 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 lwmi_fan_info 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_constraint | +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 exposing 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-channel 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 with caution.");
+
+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
+
+/**
+ * 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 = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
+ args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
+ args.arg1 = set ? *val : 0;
+
+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
+ (unsigned char *)&args, sizeof(args), &retval);
+ if (err)
+ return err;
+
+ if (!set)
+ *val = retval;
+ else if (retval != 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_sensor_types type,
+ u32 attr, int channel)
+{
+ struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
+ bool visible = false;
+
+ if (type == 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 >= 0 &&
+ priv->fan_info[channel].max_rpm >= 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 = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
+ break;
+ case hwmon_fan_min:
+ visible = priv->fan_info[channel].min_rpm >= 0;
+ break;
+ case hwmon_fan_max:
+ visible = priv->fan_info[channel].max_rpm >= 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 = dev_get_drvdata(dev);
+ u32 retval = 0;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_input:
+ err = lwmi_om_fan_get_set(priv, channel, &retval, false);
+ if (err)
+ return err;
+
+ *val = retval;
+ return 0;
+ case hwmon_fan_enable:
+ *val = priv->fan_info[channel].last_target != LWMI_FAN_STOP_RPM;
+ return 0;
+ case hwmon_fan_target:
+ if (priv->fan_info[channel].last_target == LWMI_FAN_STOP_RPM)
+ return -EBUSY;
+
+ *val = priv->fan_info[channel].last_target;
+ return 0;
+ case hwmon_fan_min:
+ *val = priv->fan_info[channel].min_rpm;
+ return 0;
+ case hwmon_fan_max:
+ *val = 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 = dev_get_drvdata(dev);
+ u32 raw, min_rpm, max_rpm;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_enable:
+ if (val == 0)
+ raw = LWMI_FAN_STOP_RPM;
+ else if (val == 1)
+ raw = 0; /* auto */
+ else
+ return -EINVAL;
+
+ goto fan_set;
+ case hwmon_fan_target:
+ if (val == 0) {
+ raw = 0;
+ goto fan_set;
+ }
+
+ min_rpm = relax_fan_constraint
+ ? LWMI_FAN_STOP_RPM + 1
+ : priv->fan_info[channel].min_rpm;
+ max_rpm = relax_fan_constraint
+ ? U16_MAX
+ : priv->fan_info[channel].max_rpm;
+
+ if (val < min_rpm || val > max_rpm)
+ return -EDOM;
+
+ raw = val;
+fan_set:
+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
+ if (err)
+ return err;
+
+ priv->fan_info[channel].last_target = raw;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
+ /* 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
+};
+
+static const struct hwmon_ops lwmi_om_hwmon_ops = {
+ .is_visible = lwmi_om_hwmon_is_visible,
+ .read = lwmi_om_hwmon_read,
+ .write = lwmi_om_hwmon_write,
+};
+
+static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
+ .ops = &lwmi_om_hwmon_ops,
+ .info = 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 = 0;
+ for (i = 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 = 0;
+ valid--;
+ }
+ }
+
+ if (valid == 0) {
+ dev_warn(&priv->wdev->dev,
+ "fan reporting/tuning is unsupported on this device\n");
+ return;
+ }
+
+ priv->hwmon_dev = 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 = 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 = 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() may 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 = 0; i < LWMI_FAN_NR; i++) {
+ priv->fan_info[i] = (struct lwmi_fan_info) {
+ .supported = 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 interface.
+ */
+ .last_target = 0,
+ .min_rpm = -ENODATA,
+ .max_rpm = -ENODATA,
+ };
+ }
+
+ priv->fan_flags.capdata00_collected = false;
+ priv->fan_flags.capdata_fan_collected = 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 = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
+ priv->fan_info[i].supported = err ? 0 : capdata00.supported;
+ }
+
+ priv->fan_flags.capdata00_collected = 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 = 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 = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
+ if (err)
+ continue;
+
+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
+ }
+
+out:
+ priv->fan_flags.capdata_fan_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
+
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
@@ -559,32 +1011,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
device_unregister(priv->fw_attr_dev);
}
+/* ======== Self (master: lenovo-wmi-other) ======== */
+
/**
* 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 all
- * 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 while 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 registered
+ * 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 = dev_get_drvdata(dev);
- struct lwmi_cd_binder binder = {};
+ struct lwmi_cd_binder binder = {
+ .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
+ };
int ret;
+ lwmi_om_fan_info_init(priv);
+
ret = component_bind_all(dev, &binder);
if (ret)
return ret;
+ priv->cd00_list = binder.cd00_list;
priv->cd01_list = binder.cd01_list;
- if (!priv->cd01_list)
+ if (!priv->cd00_list || !priv->cd01_list)
return -ENODEV;
+ lwmi_om_fan_info_collect_cd00(priv);
+
return lwmi_om_fw_attr_add(priv);
}
@@ -592,15 +1057,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 = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
+
+ lwmi_om_hwmon_remove(priv);
+
component_unbind_all(dev, NULL);
}
@@ -665,5 +1133,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 <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (6 preceding siblings ...)
2025-11-25 19:49 ` [PATCH v7 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Rong Zhang
@ 2025-11-25 21:43 ` Derek John Clark
2025-11-26 13:54 ` Rong Zhang
7 siblings, 1 reply; 10+ messages in thread
From: Derek John Clark @ 2025-11-25 21:43 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Tue, Nov 25, 2025 at 11:50 AM Rong Zhang <i@rong.moe> wrote:
>
> Lenovo WMI Other Mode interface also supports querying or setting fan
> speed RPM. This capability is described by LENOVO_CAPABILITY_DATA_00.
> Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
> cooling fans, including minimum and maximum fan speed RPM.
>
> This patchset turns lenovo-wmi-capdata01 into a unified driver (now
> named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
> LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
>
> - fanX_enable: enable/disable the fan (tunable)
> - fanX_input: current RPM
> - fanX_max: maximum RPM
> - fanX_min: minimum RPM
> - fanX_target: target RPM (tunable)
>
> LENOVO_CAPABILITY_DATA_{00,01} presents on all devices, so
> both binds to lenovo-wmi-other. However, some device does not have
> LENOVO_FAN_TEST_DATA and its presence is described by
> LENOVO_CAPABILITY_DATA_00; hence, the former binds to the latter and a
> callback is used to pass the data to lenovo-wmi-other.
>
> 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 presence of WMI GUIDs
> - The notifier framework cannot cleanly connect capdata_fan to
> lenovo-wmi-other without introducing assumptions on probing sequence
>
> capdata00 is registered as a component and a sub-master on probe,
> instead of chaining the registrations 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.
>
> The implementation does not rely on a specific binding sequence. This
> has been fuzz-tested using:
>
> #!/bin/bash
>
> DRV_DIR=/sys/bus/wmi/drivers/lenovo_wmi_cd
> CAPDATA_GUIDS=(
> $(find "$DRV_DIR"/ -name '*-*-*-*-*-*' -printf "%f ")
> )
>
> b() { sudo tee "$DRV_DIR"/bind <<<"$1"; }
> u() { sudo tee "$DRV_DIR"/unbind <<<"$1"; }
>
> for guid in "${CAPDATA_GUIDS[@]}"; do
> u "$guid"
> done
>
> while read -rsa perm; do
> for guid in "${perm[@]}"; do
> b "$guid"
> done
> sensors | grep -A3 lenovo_wmi_other || true
> for guid in "${perm[@]}"; do
> u "$guid"
> done
> done < <(python3 -c "
> from itertools import permutations
> ps = permutations('${CAPDATA_GUIDS[*]}'.split())
> for p in ps: print(' '.join(p))")
>
> for guid in "${CAPDATA_GUIDS[@]}"; do
> b "$guid"
> done
>
> Tested on ThinkBook 14 G7+ ASP.
>
> Changes in v7:
> - Fix missing #include (thanks Ilpo Järvinen)
> - Fix formatting issues (ditto)
> - dev_dbg() instead of dev_info() on probe success (ditto)
> - Rearrange to drop some gotos (ditto)
> - Move the declarations of __free()-managed variables to where they are
> assigned (ditto)
> - Improve the readability of struct definition and error paths (ditto)
> - Prevent back-and-forth changes (ditto)
> - Fix mistakenly inverted boundary check
> - Emit unaligned access to Fan Test Data's WMI block
> - Properly calculate array index when we truncate Fan Test Data
> - Fix typo
> - Link to v6: https://lore.kernel.org/r/20251122184522.18677-1-i@rong.moe/
>
> Changes in v6:
> - Fix mistaken error paths
> - Link to v5: https://lore.kernel.org/r/20251114175927.52533-1-i@rong.moe/
>
> Changes in v5:
> - Do not cast pointer to non-pointer or vice versa (thanks kernel test
> robot)
> - Fix missing include (ditto)
> - Link to v4: https://lore.kernel.org/r/20251113191152.96076-1-i@rong.moe/
>
> Changes in v4:
> - Get rid of wmi_has_guid() (thanks Armin Wolf's inspiration)
> - Add [PATCH v4 6/7], please review & test
> - Check 0x04050000.supported and bind capdata_fan to capdata00
> - Rework HWMON registration
> - 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 presence of
> capdata_fan
> - Drop Reviewed-by & Tested-by from [PATCH v4 7/7] due to the changes,
> please review & test
> - Link to v3: https://lore.kernel.org/r/20251031155349.24693-1-i@rong.moe/
>
> Changes in v3:
> - Fix grammar (thanks Derek J. Clark)
> - Link to v2: https://lore.kernel.org/r/20251030193955.107148-1-i@rong.moe/
>
> Changes in v2:
> - Add a workaround for ACPI methods that return a 4B buffer for u32
> (thanks Armin Wolf)
> - Fix function documentation (thanks kernel test bot)
> - Reword documentation (thanks Derek J. Clark)
> - Squash min/max reporting patch into the initial HWMON 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)
> - Do not register HWMON device if no fan can be exposed
> - fanX_target: Return -EBUSY instead of raw target value when fan stops
> - Link to v1: https://lore.kernel.org/r/20251019210450.88830-1-i@rong.moe/
>
> Rong Zhang (7):
> platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32
> platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
> platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
> Data
> platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
> platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
> platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
> platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning
>
> .../wmi/devices/lenovo-wmi-other.rst | 43 +-
> drivers/platform/x86/lenovo/Kconfig | 5 +-
> drivers/platform/x86/lenovo/Makefile | 2 +-
> drivers/platform/x86/lenovo/wmi-capdata.c | 812 ++++++++++++++++++
> drivers/platform/x86/lenovo/wmi-capdata.h | 65 ++
> drivers/platform/x86/lenovo/wmi-capdata01.c | 302 -------
> drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
> drivers/platform/x86/lenovo/wmi-helpers.c | 21 +-
> drivers/platform/x86/lenovo/wmi-other.c | 515 ++++++++++-
> 9 files changed, 1433 insertions(+), 357 deletions(-)
> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
>
>
> base-commit: ac3fd01e4c1efce8f2c054cdeb2ddd2fc0fb150d
> --
> 2.51.0
>
Rong,
Everything seems to be working as expected on unsupported devices.
[ 4.398189] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
362A3AFE-3D96-4665-8530-96DAD5BB300E-13 (ops lwmi_cd_component_ops
[lenovo_wmi_capdata])
[ 4.398203] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
7A8F5407-CB67-4D6E-B547-39B3BE018154-9 (ops lwmi_cd_component_ops
[lenovo_wmi_capdata])
[ 4.398208] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: fan reporting/tuning is
unsupported on this device
Obviously I can only test so far, but for where it makes sense on the
series (through to 6/7) where it is missing you can add my tag
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
And for the entire series where it is missing:
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com
As an aside, I'll likely need you to test a future patch series I'm
working on for regressions after this is accepted as I will touch the
hwmon interface a lot when adding fan curve auto points from the
cd01/om interfaces.
Thanks,
- Derek
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
2025-11-25 21:43 ` [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
@ 2025-11-26 13:54 ` Rong Zhang
0 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-26 13:54 UTC (permalink / raw)
To: Derek John Clark
Cc: Rong Zhang, Mark Pearson, Armin Wolf, Hans de Goede,
Ilpo Järvinen, Guenter Roeck, platform-driver-x86,
linux-kernel, linux-hwmon
Hi Derek,
On Tue, 2025-11-25 at 13:43 -0800, Derek John Clark wrote:
> On Tue, Nov 25, 2025 at 11:50 AM Rong Zhang <i@rong.moe> wrote:
> >
> > Lenovo WMI Other Mode interface also supports querying or setting fan
> > speed RPM. This capability is described by LENOVO_CAPABILITY_DATA_00.
> > Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
> > cooling fans, including minimum and maximum fan speed RPM.
> >
> > This patchset turns lenovo-wmi-capdata01 into a unified driver (now
> > named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
> > LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
> >
> > - fanX_enable: enable/disable the fan (tunable)
> > - fanX_input: current RPM
> > - fanX_max: maximum RPM
> > - fanX_min: minimum RPM
> > - fanX_target: target RPM (tunable)
> >
> > LENOVO_CAPABILITY_DATA_{00,01} presents on all devices, so
> > both binds to lenovo-wmi-other. However, some device does not have
> > LENOVO_FAN_TEST_DATA and its presence is described by
> > LENOVO_CAPABILITY_DATA_00; hence, the former binds to the latter and a
> > callback is used to pass the data to lenovo-wmi-other.
> >
> > 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 presence of WMI GUIDs
> > - The notifier framework cannot cleanly connect capdata_fan to
> > lenovo-wmi-other without introducing assumptions on probing sequence
> >
> > capdata00 is registered as a component and a sub-master on probe,
> > instead of chaining the registrations 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.
> >
> > The implementation does not rely on a specific binding sequence. This
> > has been fuzz-tested using:
> >
> > #!/bin/bash
> >
> > DRV_DIR=/sys/bus/wmi/drivers/lenovo_wmi_cd
> > CAPDATA_GUIDS=(
> > $(find "$DRV_DIR"/ -name '*-*-*-*-*-*' -printf "%f ")
> > )
> >
> > b() { sudo tee "$DRV_DIR"/bind <<<"$1"; }
> > u() { sudo tee "$DRV_DIR"/unbind <<<"$1"; }
> >
> > for guid in "${CAPDATA_GUIDS[@]}"; do
> > u "$guid"
> > done
> >
> > while read -rsa perm; do
> > for guid in "${perm[@]}"; do
> > b "$guid"
> > done
> > sensors | grep -A3 lenovo_wmi_other || true
> > for guid in "${perm[@]}"; do
> > u "$guid"
> > done
> > done < <(python3 -c "
> > from itertools import permutations
> > ps = permutations('${CAPDATA_GUIDS[*]}'.split())
> > for p in ps: print(' '.join(p))")
> >
> > for guid in "${CAPDATA_GUIDS[@]}"; do
> > b "$guid"
> > done
> >
> > Tested on ThinkBook 14 G7+ ASP.
> >
> > Changes in v7:
> > - Fix missing #include (thanks Ilpo Järvinen)
> > - Fix formatting issues (ditto)
> > - dev_dbg() instead of dev_info() on probe success (ditto)
> > - Rearrange to drop some gotos (ditto)
> > - Move the declarations of __free()-managed variables to where they are
> > assigned (ditto)
> > - Improve the readability of struct definition and error paths (ditto)
> > - Prevent back-and-forth changes (ditto)
> > - Fix mistakenly inverted boundary check
> > - Emit unaligned access to Fan Test Data's WMI block
> > - Properly calculate array index when we truncate Fan Test Data
> > - Fix typo
> > - Link to v6: https://lore.kernel.org/r/20251122184522.18677-1-i@rong.moe/
> >
> > Changes in v6:
> > - Fix mistaken error paths
> > - Link to v5: https://lore.kernel.org/r/20251114175927.52533-1-i@rong.moe/
> >
> > Changes in v5:
> > - Do not cast pointer to non-pointer or vice versa (thanks kernel test
> > robot)
> > - Fix missing include (ditto)
> > - Link to v4: https://lore.kernel.org/r/20251113191152.96076-1-i@rong.moe/
> >
> > Changes in v4:
> > - Get rid of wmi_has_guid() (thanks Armin Wolf's inspiration)
> > - Add [PATCH v4 6/7], please review & test
> > - Check 0x04050000.supported and bind capdata_fan to capdata00
> > - Rework HWMON registration
> > - 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 presence of
> > capdata_fan
> > - Drop Reviewed-by & Tested-by from [PATCH v4 7/7] due to the changes,
> > please review & test
> > - Link to v3: https://lore.kernel.org/r/20251031155349.24693-1-i@rong.moe/
> >
> > Changes in v3:
> > - Fix grammar (thanks Derek J. Clark)
> > - Link to v2: https://lore.kernel.org/r/20251030193955.107148-1-i@rong.moe/
> >
> > Changes in v2:
> > - Add a workaround for ACPI methods that return a 4B buffer for u32
> > (thanks Armin Wolf)
> > - Fix function documentation (thanks kernel test bot)
> > - Reword documentation (thanks Derek J. Clark)
> > - Squash min/max reporting patch into the initial HWMON 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)
> > - Do not register HWMON device if no fan can be exposed
> > - fanX_target: Return -EBUSY instead of raw target value when fan stops
> > - Link to v1: https://lore.kernel.org/r/20251019210450.88830-1-i@rong.moe/
> >
> > Rong Zhang (7):
> > platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32
> > platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
> > platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
> > Data
> > platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
> > platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
> > platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
> > platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning
> >
> > .../wmi/devices/lenovo-wmi-other.rst | 43 +-
> > drivers/platform/x86/lenovo/Kconfig | 5 +-
> > drivers/platform/x86/lenovo/Makefile | 2 +-
> > drivers/platform/x86/lenovo/wmi-capdata.c | 812 ++++++++++++++++++
> > drivers/platform/x86/lenovo/wmi-capdata.h | 65 ++
> > drivers/platform/x86/lenovo/wmi-capdata01.c | 302 -------
> > drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
> > drivers/platform/x86/lenovo/wmi-helpers.c | 21 +-
> > drivers/platform/x86/lenovo/wmi-other.c | 515 ++++++++++-
> > 9 files changed, 1433 insertions(+), 357 deletions(-)
> > create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
> > create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
> > delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
> > delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
> >
> >
> > base-commit: ac3fd01e4c1efce8f2c054cdeb2ddd2fc0fb150d
> > --
> > 2.51.0
> >
>
> Rong,
>
> Everything seems to be working as expected on unsupported devices.
>
> [ 4.398189] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
> 362A3AFE-3D96-4665-8530-96DAD5BB300E-13 (ops lwmi_cd_component_ops
> [lenovo_wmi_capdata])
> [ 4.398203] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
> 7A8F5407-CB67-4D6E-B547-39B3BE018154-9 (ops lwmi_cd_component_ops
> [lenovo_wmi_capdata])
> [ 4.398208] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: fan reporting/tuning is
> unsupported on this device
>
> Obviously I can only test so far, but for where it makes sense on the
> series (through to 6/7) where it is missing you can add my tag
> Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
>
> And for the entire series where it is missing:
> Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com
Thanks for your review and testing.
> As an aside, I'll likely need you to test a future patch series I'm
> working on for regressions after this is accepted as I will touch the
> hwmon interface a lot when adding fan curve auto points from the
> cd01/om interfaces.
Sure. Feel free to get in touch with me onlist or offlist when needed
;-)
> Thanks,
> - Derek
Thanks,
Rong
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-11-26 13:59 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-25 19:49 [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-25 19:49 ` [PATCH v7 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
2025-11-25 19:49 ` [PATCH v7 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
2025-11-25 19:49 ` [PATCH v7 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
2025-11-25 19:49 ` [PATCH v7 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
2025-11-25 19:49 ` [PATCH v7 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
2025-11-25 19:49 ` [PATCH v7 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
2025-11-25 19:49 ` [PATCH v7 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Rong Zhang
2025-11-25 21:43 ` [PATCH v7 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
2025-11-26 13:54 ` Rong Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).