* [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware
@ 2026-09-13 20:50 Rong Zhang
2026-09-13 20:50 ` [PATCH 1/9] platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary Rong Zhang
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
Some devices do not support LENOVO_CAPABILITY_DATA_01 and define the
query method as a stub that returns zero buffer. Unfortunately, some
devices do not implement the stub properly, causing WMI errors
(including ACPI errors).
The current lenovo-wmi-* implementation enforces the binding between
LENOVO_CAPABILITY_DATA_00+01 and LENOVO_OTHER_MODE because of a
limitation of the device component framework. When the capdata device
bailing out due to a WMI error, lenovo-wmi-other becomes unbound and
unable to provide firmware-attributes or hwmon device for the other
functional capdata device.
Therefore, WMI errors must be non-fatal in order not to break the
assumptions made by the device component famrework.
Poison the capdata device by releasing the capability data list in this
case. After that, NULL list will be passed to lenovo-wmi-other on bind.
The latter will provide whatever is available, or unbind the components
if nothing is available.
A poisoned capdata device releases or skips allocating most resources,
e.g., the capability data list and the debugfs directory. The device
itself is only used to satisfy the component dependency of lenovo-wmi-
other and coordinate with the latter about the absence of the capability
data.
Meanwhile, for devices that properly stubs the WMI query method (but
still declares >0 instances), keeping the capability data list with
empty data is meaningless and causes lenovo-wmi-other to call
lwmi_cd*_get_data() to retrieve nonexistent capdata in vain. These
capdata devices are poisoned as well to save resources.
In order to release or skip allocating most resources for poisoned
devices, some preparatory work is done in prior. With the preparatory
work, it also skips allocating most resources for the WMI devices that
declare 0 instance.
The new WMI API is also adopted to conform to the behavior of the
Windows WMI-ACPI driver and improve robustness on various WMI ACPI
method implementation.
Finally, add myself as a LENOVO drivers maintainer as previously
suggested by Derek.
Reported-by: Charles <hanker007@gmail.com>
Link: https://msgid.link/CAKtz0s8UYRQYW_0bh=0TMx47Axm-W-muEay-r3rqUBS1NHMPVw@mail.gmail.com/
Suggested-by: Derek J. Clark <derekjohn.clark@gmail.com>
Link: https://msgid.link/782FE636-A06A-4E12-9563-786374805947@gmail.com
Signed-off-by: Rong Zhang <i@rong.moe>
---
Rong Zhang (9):
platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary
platform/x86: lenovo-wmi-capdata: Store a pointer to component info
platform/x86: lenovo-wmi-capdata: Defer mutex initialization
platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary
platform/x86: lenovo-wmi-capdata: Adopt new WMI API
platform/x86: lenovo-wmi-capdata: Register component even on WMI error
platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device
platform/x86: lenovo-wmi-helpers: Adopt new WMI API
platform/x86: Add myself as LENOVO drivers maintainer
MAINTAINERS | 1 +
drivers/platform/x86/lenovo/wmi-capdata.c | 369 +++++++++++++++++++++---------
drivers/platform/x86/lenovo/wmi-helpers.c | 61 ++---
drivers/platform/x86/lenovo/wmi-other.c | 11 +-
4 files changed, 290 insertions(+), 152 deletions(-)
---
base-commit: 22098763a10d9c1340827fcf6edab66f153b27f0
change-id: a504a929-lwmi-wmi-new-api-f5344d48a86a
Thanks,
Rong
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/9] platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 2/9] platform/x86: lenovo-wmi-capdata: Store a pointer to component info Rong Zhang
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
When capdata00 declares no fan test support, there is no need to
allocate sub-master info as it's basically unused except for the
priv->sub_master->sub_component_list == ERR_PTR(-ENODEV) placeholder.
Therefore, only allocate priv->sub_master when necessary, otherwise its
absence implies the absence of sub-component. With that,
PTR_ERR(sub_component_list) == -ENODEV is repurposed for the following
changes to represent stubbed sub-component.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 36 ++++++++++++++++++-------------
1 file changed, 21 insertions(+), 15 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index d5e961566136..c9e4380a70d2 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -101,9 +101,11 @@ struct lwmi_cd_priv {
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. */
+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies stubbed
+ * sub-component.
+ */
bool registered; /* Has the sub-master been registered? */
- } *sub_master;
+ } *sub_master; /* NULL implies no sub-component. */
};
struct cd_list {
@@ -258,6 +260,12 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
case LENOVO_CAPABILITY_DATA_00:
binder->cd00_list = priv->list;
+ /* Missing sub-master info implies missing sub-component. */
+ if (!priv->sub_master) {
+ binder->cd_fan_list_cb(om_dev, NULL);
+ break;
+ }
+
priv->sub_master->master_dev = om_dev;
priv->sub_master->master_cb = binder->cd_fan_list_cb;
lwmi_cd_call_master_cb(priv);
@@ -288,13 +296,9 @@ static void lwmi_cd_component_unbind(struct device *cd_dev,
{
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
- switch (priv->list->type) {
- case LENOVO_CAPABILITY_DATA_00:
+ if (priv->sub_master) {
priv->sub_master->master_dev = NULL;
priv->sub_master->master_cb = NULL;
- return;
- default:
- return;
}
}
@@ -369,17 +373,19 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
struct component_match *master_match = NULL;
int ret;
+ /*
+ * If the sub-master has no sub-component, there is no need to allocate
+ * or register the sub-master. The master callback will be called with
+ * NULL on master <-> sub-master bind to inform the master about the
+ * absence of sub-component.
+ */
+ if (sub_component_type == CD_TYPE_NONE)
+ return 0;
+
priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
if (!priv->sub_master)
return -ENOMEM;
- if (sub_component_type == CD_TYPE_NONE) {
- /* The master callback will be called with NULL on bind. */
- priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
- priv->sub_master->registered = false;
- return 0;
- }
-
/*
* lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
* data cannot be used here. Steal one from lwmi_cd_table.
@@ -404,7 +410,7 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
*/
static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
{
- if (!priv->sub_master->registered)
+ if (!priv->sub_master || !priv->sub_master->registered)
return;
component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/9] platform/x86: lenovo-wmi-capdata: Store a pointer to component info
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
2026-09-13 20:50 ` [PATCH 1/9] platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 3/9] platform/x86: lenovo-wmi-capdata: Defer mutex initialization Rong Zhang
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
In the following changes, priv->list will become optional (i.e.,
NULLable). IOW, the type of the component cannot be reliable retrieved
from priv->list->type.
Store a pointer to component info so that the component type is always
available. In this manner, priv->list->type can be eliminated. Note that
the component type check in lwmi_cd*_get_data() must be removed due to
it being called without the availability of priv. Removing it should be
fine as the capdata APIs are really internal methods and their caller,
i.e., lenovo-wmi-other, always passes a matched list.
This also eliminates the need to pass the component type along setup
functions.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 44 +++++++++++++------------------
1 file changed, 19 insertions(+), 25 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index c9e4380a70d2..880ac444c206 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -87,6 +87,7 @@ static const struct lwmi_cd_info {
struct lwmi_cd_priv {
struct notifier_block acpi_nb; /* ACPI events */
+ const struct lwmi_cd_info *info;
struct wmi_device *wdev;
struct cd_list *list;
struct dentry *debugfs_dir;
@@ -110,7 +111,6 @@ struct lwmi_cd_priv {
struct cd_list {
struct mutex list_mutex; /* list R/W mutex */
- enum lwmi_cd_type type;
u8 count;
union {
@@ -139,7 +139,7 @@ static int lwmi_cd_match(struct device *dev, void *type)
return false;
priv = dev_get_drvdata(dev);
- return priv->list->type == *(enum lwmi_cd_type *)type;
+ return priv->info->type == *(enum lwmi_cd_type *)type;
}
/**
@@ -256,7 +256,7 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
struct lwmi_cd_binder *binder = data;
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
binder->cd00_list = priv->list;
@@ -454,14 +454,11 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
*
* Return: 0 on success, or -EINVAL.
*/
-#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _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) \
@@ -472,13 +469,13 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
return -EINVAL; \
}
-DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
+DEF_LWMI_CDXX_GET_DATA(cd00, struct capdata00);
EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
-DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
+DEF_LWMI_CDXX_GET_DATA(cd01, struct capdata01);
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
-DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
+DEF_LWMI_CDXX_GET_DATA(cd_fan, struct capdata_fan);
EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
/* ======== debugfs ======== */
@@ -552,15 +549,14 @@ static int lwmi_cd_debugfs_show(struct seq_file *s, void *data)
guard(mutex)(&priv->list->list_mutex);
- /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */
for (idx = 0; idx < priv->list->count; idx++) {
- seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx);
+ seq_printf(s, "%s[%u]:\n", priv->info->name, idx);
- if (priv->list->type == LENOVO_CAPABILITY_DATA_00)
+ if (priv->info->type == LENOVO_CAPABILITY_DATA_00)
lwmi_cd00_show(s, &priv->list->cd00[idx]);
- else if (priv->list->type == LENOVO_CAPABILITY_DATA_01)
+ else if (priv->info->type == LENOVO_CAPABILITY_DATA_01)
lwmi_cd01_show(s, &priv->list->cd01[idx]);
- else if (priv->list->type == LENOVO_FAN_TEST_DATA)
+ else if (priv->info->type == LENOVO_FAN_TEST_DATA)
lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]);
}
@@ -605,7 +601,7 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
int idx;
void *p;
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
p = &priv->list->cd00[0];
size = sizeof(priv->list->cd00[0]);
@@ -708,14 +704,13 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis
/**
* 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, enum lwmi_cd_type type)
+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
{
struct cd_list *list;
size_t list_size;
@@ -723,7 +718,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
count = wmidev_instance_count(priv->wdev);
- switch (type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
list_size = struct_size(list, cd00, count);
break;
@@ -749,7 +744,6 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
if (ret)
return ret;
- list->type = type;
list->count = count;
priv->list = list;
@@ -759,7 +753,6 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
/**
* 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
@@ -767,11 +760,11 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
*
* Return: 0 on success, or an error code.
*/
-static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+static int lwmi_cd_setup(struct lwmi_cd_priv *priv)
{
int ret;
- ret = lwmi_cd_alloc(priv, type);
+ ret = lwmi_cd_alloc(priv);
if (ret)
return ret;
@@ -838,10 +831,11 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
if (!priv)
return -ENOMEM;
+ priv->info = info;
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
- ret = lwmi_cd_setup(priv, info->type);
+ ret = lwmi_cd_setup(priv);
if (ret)
goto out;
@@ -907,7 +901,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
lwmi_cd_debugfs_remove(priv);
- switch (priv->list->type) {
+ switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
lwmi_cd_sub_master_del(priv);
fallthrough;
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/9] platform/x86: lenovo-wmi-capdata: Defer mutex initialization
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
2026-09-13 20:50 ` [PATCH 1/9] platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary Rong Zhang
2026-09-13 20:50 ` [PATCH 2/9] platform/x86: lenovo-wmi-capdata: Store a pointer to component info Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 4/9] platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary Rong Zhang
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
In the following changes, priv->list may be freed if the first call to
lwmi_cd_cache() fails due to WMI/ACPI errors, so the list_mutex must be
initialized after it in order not to break lockdep, as there is no
devm_mutex_destroy(). Considering that the first call to lwmi_cd_cache()
doesn't need serialization as there is no other reader or writer this
early, the initialization of list_mutex can be deferred.
Therefore, initialize list_mutex only after the first call to
lwmi_cd_cache() succeeds, otherwise it remains uninitialized and can be
devm_kfree()-ed.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 80 +++++++++++++++++++++++--------
1 file changed, 60 insertions(+), 20 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 880ac444c206..0123ec8f7b53 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -91,6 +91,7 @@ struct lwmi_cd_priv {
struct wmi_device *wdev;
struct cd_list *list;
struct dentry *debugfs_dir;
+ bool initialized;
/*
* A capdata device may be a component master of another capdata device.
@@ -588,14 +589,14 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
/* ======== WMI interface ======== */
/**
- * lwmi_cd_cache() - Cache all WMI data block information
+ * __lwmi_cd_cache() - Cache all WMI data block information locklessly
* @priv: lenovo-wmi-capdata driver data.
*
- * Loop through each WMI data block and cache the data.
+ * Loop through each WMI data block and cache the data locklessly.
*
* Return: 0 on success, or an error.
*/
-static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
{
size_t size;
int idx;
@@ -617,7 +618,6 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
return -EINVAL;
}
- guard(mutex)(&priv->list->list_mutex);
for (idx = 0; idx < priv->list->count; idx++, p += size) {
union acpi_object *ret_obj __free(kfree) = NULL;
@@ -635,14 +635,37 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
return 0;
}
+/**
+ * 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_cd_cache(struct lwmi_cd_priv *priv)
+{
+ if (!priv->initialized)
+ return __lwmi_cd_cache(priv);
+
+ switch (priv->info->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->list->list_mutex);
+ return __lwmi_cd_cache(priv);
+}
+
/**
* 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)
+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
{
struct cd_list *list;
size_t size;
@@ -688,6 +711,9 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis
if (!list)
return -ENOMEM;
+ list->count = count;
+ priv->list = list;
+
for (idx = 0; idx < count; idx++) {
/* Do not calculate array index using count, as it may be truncated. */
list->cd_fan[idx] = (struct capdata_fan) {
@@ -697,8 +723,7 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis
};
}
- *listptr = list;
- return count;
+ return 0;
}
/**
@@ -714,7 +739,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
{
struct cd_list *list;
size_t list_size;
- int count, ret;
+ int count;
count = wmidev_instance_count(priv->wdev);
@@ -726,11 +751,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
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;
+ return lwmi_cd_fan_list_alloc_cache(priv);
default:
return -EINVAL;
}
@@ -739,17 +760,32 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
if (!list)
return -ENOMEM;
-got_list:
- ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
- if (ret)
- return ret;
-
list->count = count;
priv->list = list;
return 0;
}
+/**
+ * lwmi_cd_finalize() - Finalize the capability data initialization
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_finalize(struct lwmi_cd_priv *priv)
+{
+ int ret;
+
+ if (priv->list) {
+ ret = devm_mutex_init(&priv->wdev->dev, &priv->list->list_mutex);
+ if (ret)
+ return ret;
+ }
+
+ priv->initialized = 1;
+ return 0;
+}
+
/**
* lwmi_cd_setup() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
@@ -768,7 +804,11 @@ static int lwmi_cd_setup(struct lwmi_cd_priv *priv)
if (ret)
return ret;
- return lwmi_cd_cache(priv);
+ ret = lwmi_cd_cache(priv);
+ if (ret)
+ return ret;
+
+ return lwmi_cd_finalize(priv);
}
/**
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 4/9] platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (2 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 3/9] platform/x86: lenovo-wmi-capdata: Defer mutex initialization Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 5/9] platform/x86: lenovo-wmi-capdata: Adopt new WMI API Rong Zhang
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
When no capability data is available, there is no need to allocate
capability data list as it's basically unused except for the
priv->list->count == 0 placeholder.
Therefore, only allocate priv->list when necessary, otherwise its
absence implies the absence of capability data. In this manner,
lenovo-wmi-other can skip registering unavailable functionalities
accordingly. Meanwhile, skip creating the debugfs directory as it
provides nothing when there is no capability data.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 43 ++++++++++++++++++++++---------
drivers/platform/x86/lenovo/wmi-other.c | 11 +++++---
2 files changed, 38 insertions(+), 16 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 0123ec8f7b53..793b5103d533 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -313,8 +313,8 @@ static const struct component_ops lwmi_cd_component_ops = {
* @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.
+ * device. On success, collect the pointer (or ERR_PTR(-ENODEV) if it's stubbed)
+ * to the sub-component list and try to call the master callback.
*
* Return: 0 on success, or an error code.
*/
@@ -328,7 +328,7 @@ static int lwmi_cd_sub_master_bind(struct device *dev)
if (ret)
return ret;
- priv->sub_master->sub_component_list = sub_component_list;
+ priv->sub_master->sub_component_list = sub_component_list ?: ERR_PTR(-ENODEV);
lwmi_cd_call_master_cb(priv);
return 0;
@@ -460,6 +460,9 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
{ \
u8 idx; \
\
+ if (WARN_ON(!list)) \
+ return -EINVAL; \
+ \
guard(mutex)(&list->list_mutex); \
for (idx = 0; idx < list->count; idx++) { \
if (list->_cdxx[idx].id != attribute_id) \
@@ -571,6 +574,9 @@ DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs);
*/
static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
{
+ if (!priv->list)
+ return;
+
priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev);
debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops);
@@ -582,6 +588,7 @@ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
*/
static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
{
+ /* Debugfs can handle NULL dir, no need to check. */
debugfs_remove_recursive(priv->debugfs_dir);
priv->debugfs_dir = NULL;
}
@@ -645,6 +652,9 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
*/
static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
{
+ if (!priv->list)
+ return 0;
+
if (!priv->initialized)
return __lwmi_cd_cache(priv);
@@ -707,6 +717,9 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
count = 0;
}
+ if (!count)
+ return 0;
+
list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
if (!list)
return -ENOMEM;
@@ -742,6 +755,8 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
int count;
count = wmidev_instance_count(priv->wdev);
+ if (!count)
+ return 0;
switch (priv->info->type) {
case LENOVO_CAPABILITY_DATA_00:
@@ -884,7 +899,9 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
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);
+ ret = priv->list
+ ? lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00)
+ : -ENODATA;
if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
sub_component_type = CD_TYPE_NONE;
@@ -905,14 +922,16 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
case LENOVO_CAPABILITY_DATA_01:
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
- ret = register_acpi_notifier(&priv->acpi_nb);
- if (ret)
- goto out;
+ if (priv->list) {
+ 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)
- goto out;
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
+ }
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
goto out;
@@ -930,7 +949,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
lwmi_cd_debugfs_add(priv);
dev_dbg(&wdev->dev, "registered %s with %u items\n",
- info->name, priv->list->count);
+ info->name, priv->list ? priv->list->count : 0);
}
return ret;
}
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index fbb32bf404f2..e6c8f6bcf050 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -1643,16 +1643,19 @@ static int lwmi_om_master_bind(struct device *dev)
priv->cd00_list = binder.cd00_list;
priv->cd01_list = binder.cd01_list;
- if (!priv->cd00_list || !priv->cd01_list) {
+ if (!priv->cd00_list && !priv->cd01_list) {
component_unbind_all(dev, NULL);
return -ENODEV;
}
- lwmi_om_fan_info_collect_cd00(priv);
- lwmi_om_psy_ext_init(priv);
+ if (priv->cd00_list) {
+ lwmi_om_fan_info_collect_cd00(priv);
+ lwmi_om_psy_ext_init(priv);
+ }
- lwmi_om_fw_attr_add(priv);
+ if (priv->cd01_list)
+ lwmi_om_fw_attr_add(priv);
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 5/9] platform/x86: lenovo-wmi-capdata: Adopt new WMI API
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (3 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 4/9] platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 6/9] platform/x86: lenovo-wmi-capdata: Register component even on WMI error Rong Zhang
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
The new WMI API supports multiple ACPI types by converting them into a
unified buffer that satisfies alignment and size requirements.
Adopt it to make our life easier.
Note that the new WMI API only accepts a few ACPI types to conform to
the behavior of the Windows WMI-ACPI driver. By adopting the new API, we
intentionally rejects improper ACPI types instead of silently ignoring
them.
Meanwhile, considering that `struct_size(block, data, count * 3)' may
overflow when calculating `count * 3', ignore Fan Test Data with count >
U8_MAX instead of caping `count'.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 72 ++++++++++++++-----------------
1 file changed, 33 insertions(+), 39 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 793b5103d533..5e66e6b52720 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -626,17 +626,19 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
}
for (idx = 0; idx < priv->list->count; idx++, p += size) {
- union acpi_object *ret_obj __free(kfree) = NULL;
+ struct wmi_buffer wbuf;
+ int ret;
- ret_obj = wmidev_block_query(priv->wdev, idx);
- if (!ret_obj)
- return -ENODEV;
-
- if (ret_obj->type != ACPI_TYPE_BUFFER ||
- ret_obj->buffer.length < size)
+ ret = wmidev_query_block(priv->wdev, idx, &wbuf, size);
+ if (ret == -ENODATA) /* The block is too short, probably stubbed. */
continue;
+ if (ret)
+ return ret;
- memcpy(p, ret_obj->buffer.pointer, size);
+ /* Capdata 01 is an extension to capdata 00. */
+ struct capdata00 *capdata __free(kfree) = wbuf.data;
+
+ memcpy(p, capdata, size);
}
return 0;
@@ -677,43 +679,35 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
*/
static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
{
+ struct wmi_buffer wbuf;
struct cd_list *list;
- size_t size;
+ int ret, idx;
u32 count;
- int idx;
- /* Emit unaligned access to u8 buffer with __packed. */
struct cd_fan_block {
u32 nr;
u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
- } __packed * block;
-
- union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
- if (!ret_obj)
- return -ENODEV;
-
- if (ret_obj->type == ACPI_TYPE_BUFFER) {
- block = (struct cd_fan_block *)ret_obj->buffer.pointer;
- size = ret_obj->buffer.length;
-
- count = size >= sizeof(*block) ? block->nr : 0;
- if (size < struct_size(block, data, count * 3)) {
- dev_warn(&priv->wdev->dev,
- "incomplete fan test data block: %zu < %zu, ignoring\n",
- size, struct_size(block, data, count * 3));
- count = 0;
- } else if (count > U8_MAX) {
- dev_warn(&priv->wdev->dev,
- "too many fans reported: %u > %u, truncating\n",
- count, U8_MAX);
- count = U8_MAX;
- }
- } else {
- /*
- * This is usually caused by a dummy ACPI method. Do not return an error
- * as failing to probe this device will result in sub-master device being
- * unbound. This behavior aligns with lwmi_cd_cache().
- */
+ };
+
+ ret = wmidev_query_block(priv->wdev, 0, &wbuf, sizeof(struct cd_fan_block));
+ if (ret == -ENODATA) /* The block is too short, probably stubbed. */
+ return 0;
+ if (ret)
+ return ret;
+
+ struct cd_fan_block *block __free(kfree) = wbuf.data;
+
+ count = block->nr;
+
+ if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, ignoring\n", count,
+ U8_MAX);
+ count = 0;
+ } else if (wbuf.length < struct_size(block, data, count * 3)) {
+ dev_warn(&priv->wdev->dev,
+ "incomplete fan test data block: %zu < %zu (%u fans), ignoring\n",
+ wbuf.length, struct_size(block, data, count * 3), count);
count = 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 6/9] platform/x86: lenovo-wmi-capdata: Register component even on WMI error
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (4 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 5/9] platform/x86: lenovo-wmi-capdata: Adopt new WMI API Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 7/9] platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device Rong Zhang
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
Some devices do not support LENOVO_CAPABILITY_DATA_01 and define the
query method as a stub that returns zero buffer. Unfortunately, some
devices do not implement the stub properly, causing WMI errors
(including ACPI errors).
The current lenovo-wmi-* implementation enforces the binding between
LENOVO_CAPABILITY_DATA_00+01 and LENOVO_OTHER_MODE because of a
limitation of the device component framework. When the capdata device
bailing out due to a WMI error, lenovo-wmi-other becomes unbound and
unable to provide firmware-attributes or hwmon device for the other
functional capdata device.
Therefore, WMI errors must be non-fatal in order not to break the
assumptions made by the device component famrework.
Poison the capdata device by releasing the capability data list in this
case. After that, NULL list will be passed to lenovo-wmi-other on bind.
The latter will provide whatever is available, or unbind the components
if nothing is available.
A poisoned capdata device releases or skips allocating most resources,
e.g., the capability data list and the debugfs directory. The device
itself is only used to satisfy the component dependency of lenovo-wmi-
other and coordinate with the latter about the absence of the capability
data.
Reported-by: Charles <hanker007@gmail.com>
Link: https://msgid.link/CAKtz0s8UYRQYW_0bh=0TMx47Axm-W-muEay-r3rqUBS1NHMPVw@mail.gmail.com/
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 71 +++++++++++++++++++++++++++++--
1 file changed, 67 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 5e66e6b52720..d4d5e8c97ddb 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -29,6 +29,7 @@
#include <linux/acpi.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/debugfs.h>
@@ -38,6 +39,7 @@
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/limits.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mutex_types.h>
@@ -45,6 +47,7 @@
#include <linux/overflow.h>
#include <linux/seq_file.h>
#include <linux/stddef.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/wmi.h>
@@ -447,7 +450,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
/*
* 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.
+ * @attribute_id: The capdata attribute ID (non-zero) to be found.
* @output: Pointer to a capdata* struct to return the data.
*
* Retrieves the capability data struct pointer for the given
@@ -460,7 +463,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = {
{ \
u8 idx; \
\
- if (WARN_ON(!list)) \
+ if (WARN_ON(!list || !attribute_id)) \
return -EINVAL; \
\
guard(mutex)(&list->list_mutex); \
@@ -595,6 +598,66 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
/* ======== WMI interface ======== */
+/**
+ * lwmi_cd_poison() - Poison the device by not providing any capability data
+ * @priv: lenovo-wmi-capdata driver data.
+ * @err: The occurred error.
+ *
+ * The Other Mode driver binds to both Capability Data 00 and 01. If either 00
+ * or 01 fails to probe, the Other Mode device will fail to provide fw-attr or
+ * hwmon device for the other functional capdata device.
+ *
+ * Therefore, WMI errors must be non-fatal in order not to break the assumptions
+ * made by the device component famrework, so that the Other Mode device can
+ * provide whatever is functional.
+ *
+ * After poisoning the device, NULL list will be passed to the Other Mode device
+ * on bind.
+ *
+ * Return: 0 if the @err is suppressed, otherwise its propagated as is.
+ */
+static int lwmi_cd_poison(struct lwmi_cd_priv *priv, int err)
+{
+ dev_warn(&priv->wdev->dev, "%s %s (%u items) due to error: %d\n",
+ priv->initialized ? "clearing" : "poisoning", priv->info->name,
+ priv->list ? priv->list->count : 0, err);
+
+ /* Simply print the warning message. */
+ if (!priv->list)
+ return priv->initialized ? err : 0;
+
+ /* Poison the device on initialization errors. */
+ if (!priv->initialized) {
+ devm_kfree(&priv->wdev->dev, priv->list);
+ priv->list = NULL;
+
+ return 0;
+ }
+
+ /*
+ * Runtime errors are transient. Simply clear all cached data and
+ * propagate the error.
+ *
+ * Since a valid attribute id is never 0 (the firmware also untilize the
+ * fact to stub some capabilities according to platform metadata),
+ * clearing the cached data effectively makes all attributes temporarily
+ * unavailable until the next notifier call.
+ */
+
+ lockdep_assert_held(&priv->list->list_mutex);
+
+ switch (priv->info->type) {
+ case LENOVO_CAPABILITY_DATA_01:
+ memset(priv->list->cd01, 0,
+ flex_array_size(priv->list, cd01, priv->list->count));
+ break;
+ default:
+ unreachable();
+ }
+
+ return err;
+}
+
/**
* __lwmi_cd_cache() - Cache all WMI data block information locklessly
* @priv: lenovo-wmi-capdata driver data.
@@ -633,7 +696,7 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
if (ret == -ENODATA) /* The block is too short, probably stubbed. */
continue;
if (ret)
- return ret;
+ return lwmi_cd_poison(priv, ret);
/* Capdata 01 is an extension to capdata 00. */
struct capdata00 *capdata __free(kfree) = wbuf.data;
@@ -693,7 +756,7 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
if (ret == -ENODATA) /* The block is too short, probably stubbed. */
return 0;
if (ret)
- return ret;
+ return lwmi_cd_poison(priv, ret); /* Print the warning message. */
struct cd_fan_block *block __free(kfree) = wbuf.data;
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 7/9] platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (5 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 6/9] platform/x86: lenovo-wmi-capdata: Register component even on WMI error Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 8/9] platform/x86: lenovo-wmi-helpers: Adopt new WMI API Rong Zhang
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
Some devices may stub the capdata device's WMI query method because it
doesn't support the relevant interfaces at all. For example, most
ThinkBook devices doesn't support GameZone or Other Mode thermal tuning,
so capdata 01 is useless and stubbed.
Keeping the capability data list with empty data is meaningless and
causes lenovo-wmi-other to call lwmi_cd*_get_data() to retrieve
nonexistent capdata in vain.
Therefore, poison the device and release (or skip allocating) needless
resources, e.g., the capability data list and the debugfs directory.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 33 +++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index d4d5e8c97ddb..805e36ef7f31 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -668,6 +668,7 @@ static int lwmi_cd_poison(struct lwmi_cd_priv *priv, int err)
*/
static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
{
+ bool got_data = false;
size_t size;
int idx;
void *p;
@@ -702,8 +703,31 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv)
struct capdata00 *capdata __free(kfree) = wbuf.data;
memcpy(p, capdata, size);
+
+ /*
+ * A valid attribute always has a non-zero id. IOW, a zero id
+ * implies a stubbed item (or the query method being stubbed).
+ *
+ * The stubbed item is still copied to the cache slot anyway, in
+ * case the firmware dynamically stubs the capdata item after
+ * switching Game Zone mode. In this manner, the previously
+ * exposed capdata can be void.
+ */
+ if (capdata->id)
+ got_data = true;
}
+ /*
+ * Some devices may stub the query method because it doesn't support the
+ * relevant interfaces at all. For example, most ThinkBook devices
+ * doesn't support Game Zone or Other Mode thermal tuning, so capdata 01
+ * is useless and stubbed.
+ *
+ * Poison the device and release needless resources.
+ */
+ if (!got_data)
+ return lwmi_cd_poison(priv, -ENODATA);
+
return 0;
}
@@ -743,6 +767,7 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
{
struct wmi_buffer wbuf;
+ bool got_data = false;
struct cd_list *list;
int ret, idx;
u32 count;
@@ -785,14 +810,22 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv)
priv->list = list;
for (idx = 0; idx < count; idx++) {
+ if (!block->data[idx]) /* Ignore stub. */
+ continue;
+
/* Do not calculate array index using count, as it may be truncated. */
list->cd_fan[idx] = (struct capdata_fan) {
.id = block->data[idx],
.max_rpm = block->data[idx + block->nr],
.min_rpm = block->data[idx + (2 * block->nr)],
};
+
+ got_data = true;
}
+ if (!got_data)
+ return lwmi_cd_poison(priv, -ENODATA);
+
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 8/9] platform/x86: lenovo-wmi-helpers: Adopt new WMI API
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (6 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 7/9] platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-13 20:50 ` [PATCH 9/9] platform/x86: Add myself as LENOVO drivers maintainer Rong Zhang
2026-09-26 21:04 ` [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Navon John Lukose
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
The new WMI API supports multiple ACPI types by converting them into a
unified buffer that satisfies alignment and size requirements.
Adopt it to make our life easier.
Note that the Windows WMI-ACPI driver converts all ACPI objects into a
common buffer format, so adopting the new API intentionally accepts more
ACPI types as a followup of commit 465dc9da8ff6 ("platform/x86:
lenovo-wmi-helpers: Convert returned buffer into u32").
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>
---
drivers/platform/x86/lenovo/wmi-helpers.c | 61 ++++++++++++-------------------
1 file changed, 23 insertions(+), 38 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index 8f5766c391eb..687caa226263 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -24,9 +24,10 @@
#include <linux/export.h>
#include <linux/module.h>
#include <linux/notifier.h>
-#include <linux/unaligned.h>
#include <linux/wmi.h>
+#include <asm/byteorder.h>
+
#include "wmi-helpers.h"
/* Thermal mode notifier chain. */
@@ -42,49 +43,33 @@ static BLOCKING_NOTIFIER_HEAD(tm_chain_head);
* @size: Length of the buffer.
* @retval: Pointer for the return value to be assigned.
*
- * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
- * integer. Validates the return value type and assigns the value to the
- * retval pointer.
+ * Calls the specified WMI method for Lenovo WMI devices that return a WMI
+ * integer. Validates the return value and assigns it to @retval when it's not
+ * NULL, otherwise the return value is not validated.
*
* Return: 0 on success, or an error code.
*/
int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
unsigned char *buf, size_t size, u32 *retval)
{
- struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
- struct acpi_buffer input = { size, buf };
- acpi_status status;
-
- status = wmidev_evaluate_method(wdev, instance, method_id, &input,
- &output);
- if (ACPI_FAILURE(status))
- return -EIO;
-
- union acpi_object *ret_obj __free(kfree) = output.pointer;
-
- if (retval) {
- if (!ret_obj)
- return -ENODATA;
-
- switch (ret_obj->type) {
- /*
- * The ACPI method may simply return a buffer when a u32
- * is expected. This is valid on Windows as its WMI-ACPI
- * driver converts everything to a common buffer.
- */
- case ACPI_TYPE_BUFFER:
- if (ret_obj->buffer.length < sizeof(u32))
- return -ENXIO;
-
- *retval = get_unaligned_le32(ret_obj->buffer.pointer);
- return 0;
- case ACPI_TYPE_INTEGER:
- *retval = (u32)ret_obj->integer.value;
- return 0;
- default:
- return -ENXIO;
- }
- }
+ struct wmi_buffer input = {
+ .length = size,
+ .data = buf,
+ };
+ struct wmi_buffer output;
+ int ret;
+
+ if (!retval)
+ return wmidev_invoke_procedure(wdev, instance, method_id, &input);
+
+ ret = wmidev_invoke_method(wdev, instance, method_id, &input,
+ &output, sizeof(__le32));
+ if (ret)
+ return ret;
+
+ __le32 *valp __free(kfree) = output.data;
+
+ *retval = le32_to_cpu(*valp);
return 0;
};
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 9/9] platform/x86: Add myself as LENOVO drivers maintainer
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (7 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 8/9] platform/x86: lenovo-wmi-helpers: Adopt new WMI API Rong Zhang
@ 2026-09-13 20:50 ` Rong Zhang
2026-09-26 21:04 ` [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Navon John Lukose
9 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-09-13 20:50 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf
Cc: Charles, platform-driver-x86, linux-kernel, Rong Zhang
I regularly improve LENOVO drivers (including lenovo-wmi-* and
ideapad-laptop) and review patches posted on the pdx86 mailing list
along with Derek J. Clark.
Add myself as a LENOVO drivers maintainer to reflect this.
Suggested-by: Derek J. Clark <derekjohn.clark@gmail.com>
Link: https://msgid.link/782FE636-A06A-4E12-9563-786374805947@gmail.com
Signed-off-by: Rong Zhang <i@rong.moe>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c2414447892c..d69be560ee98 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14805,6 +14805,7 @@ F: drivers/usb/misc/legousbtower.c
LENOVO drivers
M: Mark Pearson <mpearson-lenovo@squebb.ca>
M: Derek J. Clark <derekjohn.clark@gmail.com>
+M: Rong Zhang <i@rong.moe>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
` (8 preceding siblings ...)
2026-09-13 20:50 ` [PATCH 9/9] platform/x86: Add myself as LENOVO drivers maintainer Rong Zhang
@ 2026-09-26 21:04 ` Navon John Lukose
9 siblings, 0 replies; 11+ messages in thread
From: Navon John Lukose @ 2026-09-26 21:04 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Derek J. Clark, Hans de Goede, Ilpo Järvinen,
Armin Wolf, Charles, platform-driver-x86, linux-kernel,
Navon John Lukose
Tested on a Yoga Pro 7 14IAH10 (83KF, BIOS QGCN35WW). Neither mainline
nor the series binds here. This firmware has no
LENOVO_CAPABILITY_DATA_01 in _WDG at all, so no component ever
registers for that match and lwmi_om_master_bind() never runs. Skipping
the match for GUIDs the firmware doesn't declare fixes it:
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 805e36ef7..d64520be1 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -76,11 +76,13 @@ enum lwmi_cd_type {
#define LWMI_CD_TABLE_ITEM(_type) \
[_type] = { \
.name = #_type, \
+ .guid = _type##_GUID, \
.type = _type, \
}
static const struct lwmi_cd_info {
const char *name;
+ const char *guid;
enum lwmi_cd_type type;
} lwmi_cd_table[] = {
LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
@@ -166,6 +168,14 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match
if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
continue;
+ /*
+ * Some firmware does not declare every capdata GUID at all, in
+ * which case no component would ever register for it and the
+ * master could never bind.
+ */
+ if (!wmi_has_guid(lwmi_cd_table[i].guid))
+ continue;
+
component_match_add(master, matchptr, lwmi_cd_match,
(void *)&lwmi_cd_table[i].type);
if (IS_ERR(*matchptr))
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index e6c8f6bcf..b196e9926 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -1704,6 +1704,8 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
lwmi_cd_match_add_all(&wdev->dev, &master_match);
if (IS_ERR(master_match))
return PTR_ERR(master_match);
+ if (!master_match)
+ return -ENODEV;
return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
master_match);
With that on top (built out of tree against 7.2.5), lenovo_wmi_other
binds and exposes fan1 and fan4 (min 1500/1800, max 5100/5400, input
matching the EC tach fields). fanX_target pins both fans, 0 hands them
back to the firmware curve, and out-of-range writes get -EINVAL. With
the change above:
Tested-by: Navon John Lukose <navonjohnlukose@gmail.com>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-09-26 21:04 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-13 20:50 [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Rong Zhang
2026-09-13 20:50 ` [PATCH 1/9] platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary Rong Zhang
2026-09-13 20:50 ` [PATCH 2/9] platform/x86: lenovo-wmi-capdata: Store a pointer to component info Rong Zhang
2026-09-13 20:50 ` [PATCH 3/9] platform/x86: lenovo-wmi-capdata: Defer mutex initialization Rong Zhang
2026-09-13 20:50 ` [PATCH 4/9] platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary Rong Zhang
2026-09-13 20:50 ` [PATCH 5/9] platform/x86: lenovo-wmi-capdata: Adopt new WMI API Rong Zhang
2026-09-13 20:50 ` [PATCH 6/9] platform/x86: lenovo-wmi-capdata: Register component even on WMI error Rong Zhang
2026-09-13 20:50 ` [PATCH 7/9] platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device Rong Zhang
2026-09-13 20:50 ` [PATCH 8/9] platform/x86: lenovo-wmi-helpers: Adopt new WMI API Rong Zhang
2026-09-13 20:50 ` [PATCH 9/9] platform/x86: Add myself as LENOVO drivers maintainer Rong Zhang
2026-09-26 21:04 ` [PATCH 0/9] platform/x86: lenovo-wmi-{other,capdata,helpers}: Improve robustness on buggy firmware Navon John Lukose
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®