* [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
@ 2025-11-13 19:11 Rong Zhang
2025-11-13 19:11 ` [PATCH v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 decribed 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.
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 v4:
- Get rid of wmi_has_guid() (thanks Armin Wolf's inspiration)
- Add [PATCH v4 6/7], please review & test
- Rework HWMON registration
- Collect fan into 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 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 | 804 ++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 64 ++
drivers/platform/x86/lenovo/wmi-capdata01.c | 302 -------
drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
drivers/platform/x86/lenovo/wmi-helpers.c | 22 +-
drivers/platform/x86/lenovo/wmi-other.c | 511 ++++++++++-
9 files changed, 1421 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: 2ccec5944606ee1389abc7ee41986825c6ceb574
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-13 19:11 ` [PATCH v4 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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>
---
Changes in v4:
- Accept oversized buffer (thanks Armin Wolf)
- Use get_unaligned_le32() (ditto)
Changes in v2:
- New patch (thanks Armin Wolf)
---
drivers/platform/x86/lenovo/wmi-helpers.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index f6fef6296251..ea0b75334c9e 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -21,6 +21,7 @@
#include <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,25 @@ 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 >= 4)
+ 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 v4 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-13 19:11 ` [PATCH v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-13 19:11 ` [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-13 19:11 ` [PATCH v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
2025-11-13 19:11 ` [PATCH v4 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-14 13:50 ` kernel test robot
2025-11-13 19:11 ` [PATCH v4 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
` (3 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 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 | 226 +++++++++++++++++-----
drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
drivers/platform/x86/lenovo/wmi-other.c | 16 +-
3 files changed, 190 insertions(+), 59 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index c5e74b2bfeb3..a46208212a98 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -12,8 +12,13 @@
*
* Copyright (C) 2025 Derek J. Clark <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/cleanup.h>
#include <linux/component.h>
@@ -36,6 +41,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 +66,64 @@ 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.
+ * @data: Type of the capability data (cast to enum lwmi_cd_type).
+ *
+ * Return: int.
+ */
+static int lwmi_cd_match(struct device *dev, void *data)
+{
+ enum lwmi_cd_type type = (enum lwmi_cd_type)data;
+ struct lwmi_cd_priv *priv;
+
+ if (dev->driver != &lwmi_cd_driver.driver)
+ return false;
+
+ priv = dev_get_drvdata(dev);
+ return priv->list->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 +135,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 +153,33 @@ 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 +192,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 +214,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 +226,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 +257,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 +267,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 +275,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 +335,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 +349,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_info(&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 +415,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..f2e1e34d58a9 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -579,14 +579,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_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 = { 0 };
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 v4 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (2 preceding siblings ...)
2025-11-13 19:11 ` [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-13 19:11 ` [PATCH v4 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 a46208212a98..aeeb59c4c982 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
@@ -36,12 +39,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,
};
@@ -55,6 +60,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),
};
@@ -70,6 +76,7 @@ struct cd_list {
u8 count;
union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
DECLARE_FLEX_ARRAY(struct capdata01, cd01);
};
};
@@ -138,6 +145,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;
@@ -179,6 +189,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");
@@ -197,6 +210,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]);
@@ -242,6 +259,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;
@@ -354,6 +374,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;
@@ -387,6 +410,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;
@@ -400,6 +424,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 v4 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (3 preceding siblings ...)
2025-11-13 19:11 ` [PATCH v4 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-13 19:11 ` [PATCH v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
2025-11-13 19:11 ` [PATCH v4 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Rong Zhang
6 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 v4:
- Rebase on top of changes made to [PATCH v4 3/7]
- Do not register it as a component until [PATCH v4 6/7]
Changes in v2:
- Reword documentation
---
.../wmi/devices/lenovo-wmi-other.rst | 17 +++
drivers/platform/x86/lenovo/wmi-capdata.c | 102 ++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 7 ++
3 files changed, 126 insertions(+)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index fcad595d49af..821282e07d93 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -62,6 +62,13 @@ The following firmware-attributes are implemented:
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
+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 aeeb59c4c982..8834a2752c97 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)
*
@@ -41,6 +45,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
@@ -48,6 +53,7 @@
enum lwmi_cd_type {
LENOVO_CAPABILITY_DATA_00,
LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
};
#define LWMI_CD_TABLE_ITEM(_type) \
@@ -62,6 +68,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 {
@@ -78,6 +85,7 @@ struct cd_list {
union {
DECLARE_FLEX_ARRAY(struct capdata00, cd00);
DECLARE_FLEX_ARRAY(struct capdata01, cd01);
+ DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
};
};
@@ -118,6 +126,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 optional interfaces. */
+ 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))
@@ -195,6 +207,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.
@@ -218,6 +233,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;
}
@@ -240,6 +258,78 @@ 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)
+{
+ u32 count, *fan_ids, *fan_min_rpms, *fan_max_rpms;
+ union acpi_object *ret_obj __free(kfree) = NULL;
+ struct block { u32 nr; u32 data[]; } *block;
+ struct cd_list *list;
+ size_t size;
+ int idx;
+
+ ret_obj = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in master driver being
+ * unbound - this behavior aligns with lwmi_cd_cache().
+ */
+ if (ret_obj->type != ACPI_TYPE_BUFFER) {
+ count = 0;
+ goto alloc;
+ }
+
+ size = ret_obj->buffer.length;
+ block = (struct block *)ret_obj->buffer.pointer;
+
+ 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;
+ }
+
+ if (count == 0)
+ goto alloc;
+
+ if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+
+ fan_ids = &block->data[0];
+ fan_max_rpms = &block->data[count];
+ fan_min_rpms = &block->data[count * 2];
+
+alloc:
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = fan_ids[idx],
+ .min_rpm = fan_min_rpms[idx],
+ .max_rpm = fan_max_rpms[idx],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
/**
* lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
* @priv: lenovo-wmi-capdata driver data.
@@ -265,6 +355,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;
}
@@ -273,6 +369,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;
@@ -391,6 +488,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;
}
@@ -414,6 +513,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);
}
@@ -426,6 +527,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 v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (4 preceding siblings ...)
2025-11-13 19:11 ` [PATCH v4 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
2025-11-14 9:48 ` kernel test robot
2025-11-13 19:11 ` [PATCH v4 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Rong Zhang
6 siblings, 1 reply; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 four issues:
- The component framework only supports one aggregation per master
- A binding is only established until all components are found
- The Fan Test Data interface may be missing on some devices
- To get rid of queries for the presense of WMI GUIDs
Signed-off-by: Rong Zhang <i@rong.moe>
---
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 | 257 +++++++++++++++++++++-
drivers/platform/x86/lenovo/wmi-capdata.h | 19 ++
drivers/platform/x86/lenovo/wmi-other.c | 5 -
3 files changed, 274 insertions(+), 7 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 8834a2752c97..3c4d1cbbf312 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -50,10 +50,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) \
@@ -75,6 +82,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 {
@@ -126,7 +147,7 @@ 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 optional interfaces. */
+ /* Skip sub-components. */
if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
continue;
@@ -138,6 +159,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.
@@ -148,6 +219,8 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD");
* list. This is used to call lwmi_cd*_get_data to look up attribute data
* from the lenovo-wmi-other driver.
*
+ * If cd_dev is a sub-master, try to call the master callback.
+ *
* Return: 0
*/
static int lwmi_cd_component_bind(struct device *cd_dev,
@@ -159,6 +232,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;
@@ -170,8 +248,163 @@ 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;
+ }
+
+ component_match_add(&priv->wdev->dev, &master_match,
+ lwmi_cd_match, (void *)sub_component_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) {
+ 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,
};
/**
@@ -471,9 +704,25 @@ 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);
goto out;
+ }
case LENOVO_CAPABILITY_DATA_01:
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
@@ -489,6 +738,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;
@@ -510,10 +760,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..0b3bb876d768 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -7,6 +7,17 @@
#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 +43,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 f2e1e34d58a9..b3adcc2804fa 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -54,11 +54,6 @@
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
-#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 v4 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (5 preceding siblings ...)
2025-11-13 19:11 ` [PATCH v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
@ 2025-11-13 19:11 ` Rong Zhang
6 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2025-11-13 19:11 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 v4:
- Rework HWMON registration due to the introduction of [PATCH v4 6/7]
- Collect fan into from capdata00 and capdata_fan separately
- Use a callback to collect fan info from capdata_fan
- Trigger HWMON registration only if all fan info is collected
- Do not check 0x04050000.supported, implied by the presense of
capdata_fan
- Drop Reviewed-by & Tested-by due to the changes, please review & test
Changes in v3:
- Reword documentation (thanks Derek J. Clark)
Changes in v2:
- Define 4 fan channels instead of 2 (thanks Derek J. Clark)
- Squash min/max reporting patch into this one (ditto)
- Query 0x04050000 for interface availability (ditto)
- New parameter "expose_all_fans" to skip this check
- Enforce min/max RPM constraint on set (ditto)
- New parameter "relax_fan_constraint" to disable this behavior
- Drop parameter "ignore_fan_cap", superseded by the next one
- New parameter "expose_all_fans" to expose fans w/o such data
- Assume auto mode on probe (ditto)
- Reword documentation (ditto)
- Do not register HWMON device if no fan can be exposed
- fanX_target: Return -EBUSY instead of raw target value when fan stops
---
.../wmi/devices/lenovo-wmi-other.rst | 11 +
drivers/platform/x86/lenovo/Kconfig | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 485 +++++++++++++++++-
3 files changed, 487 insertions(+), 10 deletions(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index 821282e07d93..bd1d733ff286 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -31,6 +31,8 @@ under the following path:
/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 b3adcc2804fa..b811a2fbdb60 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);
@@ -71,15 +95,439 @@ 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 fan_info {
+ u32 supported;
+ u32 last_target;
+ long min_rpm;
+ long max_rpm;
+ } fan_info[LWMI_FAN_NR];
+ struct {
+ bool capdata00_collected : 1;
+ bool capdata_fan_collected : 1;
+ } fan_flags;
+};
+
+/*
+ * Visibility of fan channels:
+ *
+ * +---------------------+---------+------------------+-----------------------+------------+
+ * | | default | +expose_all_fans | +relax_fan_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 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 +1007,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 = { 0 };
+ 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 +1053,18 @@ static int lwmi_om_master_bind(struct device *dev)
* lwmi_om_master_unbind() - Unbind all components of the other mode driver
* @dev: The lenovo-wmi-other driver basic device
*
- * Unregister all capability data attribute groups. Then call
- * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
- * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ * Unregister all firmware attribute groups and the HWMON device. Then call
+ * component_unbind_all to unbind lenovo-wmi-capdata devices from the
+ * lenovo-wmi-other master driver.
*/
static void lwmi_om_master_unbind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
+
+ lwmi_om_hwmon_remove(priv);
+
component_unbind_all(dev, NULL);
}
@@ -665,5 +1129,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <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 v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
2025-11-13 19:11 ` [PATCH v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
@ 2025-11-14 9:48 ` kernel test robot
0 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2025-11-14 9:48 UTC (permalink / raw)
To: Rong Zhang, Mark Pearson, Derek J. Clark, Armin Wolf,
Hans de Goede, Ilpo Järvinen
Cc: llvm, oe-kbuild-all, Rong Zhang, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Rong,
kernel test robot noticed the following build errors:
[auto build test ERROR on 2ccec5944606ee1389abc7ee41986825c6ceb574]
url: https://github.com/intel-lab-lkp/linux/commits/Rong-Zhang/platform-x86-lenovo-wmi-helpers-Convert-returned-buffer-into-u32/20251114-032343
base: 2ccec5944606ee1389abc7ee41986825c6ceb574
patch link: https://lore.kernel.org/r/20251113191152.96076-7-i%40rong.moe
patch subject: [PATCH v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
config: x86_64-buildonly-randconfig-001-20251114 (https://download.01.org/0day-ci/archive/20251114/202511141750.9JubdfJr-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251114/202511141750.9JubdfJr-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202511141750.9JubdfJr-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/platform/x86/lenovo/wmi-capdata.c:124:27: warning: cast to smaller integer type 'enum lwmi_cd_type' from 'void *' [-Wvoid-pointer-to-enum-cast]
124 | enum lwmi_cd_type type = (enum lwmi_cd_type)data;
| ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/platform/x86/lenovo/wmi-capdata.c:711:40: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
711 | ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
| ^
drivers/platform/x86/lenovo/wmi-capdata.c:56:3: note: expanded from macro 'LWMI_ATTR_ID_FAN_TEST'
56 | (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
| ^
1 warning and 1 error generated.
vim +/FIELD_PREP +711 drivers/platform/x86/lenovo/wmi-capdata.c
685
686 static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
687 {
688 const struct lwmi_cd_info *info = context;
689 struct lwmi_cd_priv *priv;
690 int ret;
691
692 if (!info)
693 return -EINVAL;
694
695 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
696 if (!priv)
697 return -ENOMEM;
698
699 priv->wdev = wdev;
700 dev_set_drvdata(&wdev->dev, priv);
701
702 ret = lwmi_cd_setup(priv, info->type);
703 if (ret)
704 goto out;
705
706 switch (info->type) {
707 case LENOVO_CAPABILITY_DATA_00: {
708 enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
709 struct capdata00 capdata00;
710
> 711 ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
712 if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
713 dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
714 sub_component_type = CD_TYPE_NONE;
715 }
716
717 /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
718 ret = lwmi_cd_sub_master_add(priv, sub_component_type);
719 if (ret)
720 goto out;
721
722 /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
723 ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
724 goto out;
725 }
726 case LENOVO_CAPABILITY_DATA_01:
727 priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
728
729 ret = register_acpi_notifier(&priv->acpi_nb);
730 if (ret)
731 goto out;
732
733 ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
734 &priv->acpi_nb);
735 if (ret)
736 goto out;
737
738 ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
739 goto out;
740 case LENOVO_FAN_TEST_DATA:
741 ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
742 goto out;
743 default:
744 return -EINVAL;
745 }
746 out:
747 if (ret) {
748 dev_err(&wdev->dev, "failed to register %s: %d\n",
749 info->name, ret);
750 } else {
751 dev_info(&wdev->dev, "registered %s with %u items\n",
752 info->name, priv->list->count);
753 }
754 return ret;
755 }
756
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-11-13 19:11 ` [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
@ 2025-11-14 13:50 ` kernel test robot
0 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2025-11-14 13:50 UTC (permalink / raw)
To: Rong Zhang, Mark Pearson, Derek J. Clark, Armin Wolf,
Hans de Goede, Ilpo Järvinen
Cc: llvm, oe-kbuild-all, Rong Zhang, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Rong,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 2ccec5944606ee1389abc7ee41986825c6ceb574]
url: https://github.com/intel-lab-lkp/linux/commits/Rong-Zhang/platform-x86-lenovo-wmi-helpers-Convert-returned-buffer-into-u32/20251114-032343
base: 2ccec5944606ee1389abc7ee41986825c6ceb574
patch link: https://lore.kernel.org/r/20251113191152.96076-4-i%40rong.moe
patch subject: [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
config: x86_64-randconfig-011-20251114 (https://download.01.org/0day-ci/archive/20251114/202511142143.WLGlvDdQ-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251114/202511142143.WLGlvDdQ-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202511142143.WLGlvDdQ-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/platform/x86/lenovo/wmi-capdata.c:88:27: warning: cast to smaller integer type 'enum lwmi_cd_type' from 'void *' [-Wvoid-pointer-to-enum-cast]
88 | enum lwmi_cd_type type = (enum lwmi_cd_type)data;
| ^~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
vim +88 drivers/platform/x86/lenovo/wmi-capdata.c
78
79 /**
80 * lwmi_cd_match() - Match rule for the master driver.
81 * @dev: Pointer to the capability data parent device.
82 * @data: Type of the capability data (cast to enum lwmi_cd_type).
83 *
84 * Return: int.
85 */
86 static int lwmi_cd_match(struct device *dev, void *data)
87 {
> 88 enum lwmi_cd_type type = (enum lwmi_cd_type)data;
89 struct lwmi_cd_priv *priv;
90
91 if (dev->driver != &lwmi_cd_driver.driver)
92 return false;
93
94 priv = dev_get_drvdata(dev);
95 return priv->list->type == type;
96 }
97
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-11-14 13:50 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-13 19:11 [PATCH v4 0/7] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-11-13 19:11 ` [PATCH v4 1/7] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 Rong Zhang
2025-11-13 19:11 ` [PATCH v4 2/7] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
2025-11-13 19:11 ` [PATCH v4 3/7] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
2025-11-14 13:50 ` kernel test robot
2025-11-13 19:11 ` [PATCH v4 4/7] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
2025-11-13 19:11 ` [PATCH v4 5/7] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
2025-11-13 19:11 ` [PATCH v4 6/7] platform/x86: lenovo-wmi-capdata: Wire up " Rong Zhang
2025-11-14 9:48 ` kernel test robot
2025-11-13 19:11 ` [PATCH v4 7/7] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning 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).