[PATCH v2 05/12] platform/x86: lenovo-wmi-capdata: Defer mutex initialization
From: Rong Zhang
Date: Fri Oct 09 2026 - 08:58:39 EST
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@xxxxxxxx>
---
Changes in v2:
- Synchronize the initialization with release-acquire barriers (thanks
Ilpo Järvinen)
---
drivers/platform/x86/lenovo/wmi-capdata.c | 86 +++++++++++++++++++++++--------
1 file changed, 65 insertions(+), 21 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 3008d6d699c0..e52cb2de49a6 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -48,6 +48,8 @@
#include <linux/types.h>
#include <linux/wmi.h>
+#include <asm/barrier.h>
+
#include "wmi-capdata.h"
#include "wmi-helpers.h"
@@ -91,6 +93,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 +591,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 +620,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 +637,38 @@ 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)
+{
+ /* Synchronize mutex initialization. Paired with lwmi_cd_finalize(). */
+ if (!smp_load_acquire(&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.
+ * Return: 0 on success, 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 +714,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 +726,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 +742,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 +754,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 +763,33 @@ 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;
+ }
+
+ /* Synchronize mutex initialization. Paired with lwmi_cd_cache(). */
+ smp_store_release(&priv->initialized, 1);
+ return 0;
+}
+
/**
* lwmi_cd_setup() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
@@ -768,7 +808,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