Skip to content

Commit 67d9a39

Browse files
Rongronggg9ij-intel
authored andcommitted
platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data
A capdata00 attribute (0x04050000) describes the presence of Fan Test Data. Query it, and bind Fan Test Data as a component of capdata00 accordingly. The component master of capdata00 may pass a callback while binding to retrieve fan info from Fan Test Data. Summarizing this scheme: lenovo-wmi-other <-> capdata00 <-> capdata_fan |- master |- component | |- sub-master |- sub-component The callback will be called once both the master and the sub-component are bound to the sub-master (component). This scheme is essential to solve these issues: - The component framework only supports one aggregation per master - A binding is only established until all components are found - The Fan Test Data interface may be missing on some devices - To get rid of queries for the presence of WMI GUIDs - The notifier framework cannot cleanly connect capdata_fan to lenovo-wmi-other without introducing assumptions on probing sequence capdata00 is registered as a component and a sub-master on probe, instead of chaining the registrations in one's bind callback. This is because calling (un)registration methods of the component framework causes deadlock in (un)bind callbacks, i.e., it's impossible to register capdata00 as a sub-master/component in its component/sub-master bind callback, and vice versa. Signed-off-by: Rong Zhang <i@rong.moe> Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com> Tested-by: Derek J. Clark <derekjohn.clark@gmail.com> Link: https://patch.msgid.link/20260120182104.163424-7-i@rong.moe Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
1 parent 012a8f9 commit 67d9a39

3 files changed

Lines changed: 299 additions & 6 deletions

File tree

drivers/platform/x86/lenovo/wmi-capdata.c

Lines changed: 279 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2828

2929
#include <linux/acpi.h>
30+
#include <linux/bitfield.h>
3031
#include <linux/bug.h>
3132
#include <linux/cleanup.h>
3233
#include <linux/component.h>
@@ -55,10 +56,17 @@
5556
#define ACPI_AC_CLASS "ac_adapter"
5657
#define ACPI_AC_NOTIFY_STATUS 0x80
5758

59+
#define LWMI_FEATURE_ID_FAN_TEST 0x05
60+
61+
#define LWMI_ATTR_ID_FAN_TEST \
62+
(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
63+
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
64+
5865
enum lwmi_cd_type {
5966
LENOVO_CAPABILITY_DATA_00,
6067
LENOVO_CAPABILITY_DATA_01,
6168
LENOVO_FAN_TEST_DATA,
69+
CD_TYPE_NONE = -1,
6270
};
6371

6472
#define LWMI_CD_TABLE_ITEM(_type) \
@@ -80,6 +88,20 @@ struct lwmi_cd_priv {
8088
struct notifier_block acpi_nb; /* ACPI events */
8189
struct wmi_device *wdev;
8290
struct cd_list *list;
91+
92+
/*
93+
* A capdata device may be a component master of another capdata device.
94+
* E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
95+
* |- master |- component
96+
* |- sub-master
97+
* |- sub-component
98+
*/
99+
struct lwmi_cd_sub_master_priv {
100+
struct device *master_dev;
101+
cd_list_cb_t master_cb;
102+
struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
103+
bool registered; /* Has the sub-master been registered? */
104+
} *sub_master;
83105
};
84106

85107
struct cd_list {
@@ -142,6 +164,72 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match
142164
}
143165
EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
144166

167+
/**
168+
* lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
169+
* @priv: Pointer to the capability data private data.
170+
*
171+
* Call the master callback and pass the sub-component list to it if the
172+
* dependency chain (master <-> sub-master <-> sub-component) is complete.
173+
*/
174+
static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
175+
{
176+
struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
177+
178+
/*
179+
* Call the callback only if the dependency chain is ready:
180+
* - Binding between master and sub-master: fills master_dev and master_cb
181+
* - Binding between sub-master and sub-component: fills sub_component_list
182+
*
183+
* If a binding has been unbound before the other binding is bound, the
184+
* corresponding members filled by the former are guaranteed to be cleared.
185+
*
186+
* This function is only called in bind callbacks, and the component
187+
* framework guarantees bind/unbind callbacks may never execute
188+
* simultaneously, which implies that it's impossible to have a race
189+
* condition.
190+
*
191+
* Hence, this check is sufficient to ensure that the callback is called
192+
* at most once and with the correct state, without relying on a specific
193+
* sequence of binding establishment.
194+
*/
195+
if (!sub_component_list ||
196+
!priv->sub_master->master_dev ||
197+
!priv->sub_master->master_cb)
198+
return;
199+
200+
if (PTR_ERR(sub_component_list) == -ENODEV)
201+
sub_component_list = NULL;
202+
else if (WARN_ON(IS_ERR(sub_component_list)))
203+
return;
204+
205+
priv->sub_master->master_cb(priv->sub_master->master_dev,
206+
sub_component_list);
207+
208+
/*
209+
* Userspace may unbind a device from its driver and bind it again
210+
* through sysfs. Let's call this operation "reprobe" to distinguish it
211+
* from component "rebind".
212+
*
213+
* When reprobing capdata00/01 or the master device, the master device
214+
* is unbound from us with appropriate cleanup before we bind to it and
215+
* call master_cb. Everything is fine in this case.
216+
*
217+
* When reprobing capdata_fan, the master device has never been unbound
218+
* from us (hence no cleanup is done)[1], but we call master_cb the
219+
* second time. To solve this issue, we clear master_cb and master_dev
220+
* so we won't call master_cb twice while a binding is still complete.
221+
*
222+
* Note that we can't clear sub_component_list, otherwise reprobing
223+
* capdata01 or the master device causes master_cb to be never called
224+
* after we rebind to the master device.
225+
*
226+
* [1]: The master device does not need capdata_fan in run time, so
227+
* losing capdata_fan will not break the binding to the master device.
228+
*/
229+
priv->sub_master->master_cb = NULL;
230+
priv->sub_master->master_dev = NULL;
231+
}
232+
145233
/**
146234
* lwmi_cd_component_bind() - Bind component to master device.
147235
* @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
@@ -152,6 +240,8 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
152240
* list. This is used to call lwmi_cd*_get_data to look up attribute data
153241
* from the lenovo-wmi-other driver.
154242
*
243+
* If cd_dev is a sub-master, try to call the master callback.
244+
*
155245
* Return: 0
156246
*/
157247
static int lwmi_cd_component_bind(struct device *cd_dev,
@@ -163,6 +253,11 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
163253
switch (priv->list->type) {
164254
case LENOVO_CAPABILITY_DATA_00:
165255
binder->cd00_list = priv->list;
256+
257+
priv->sub_master->master_dev = om_dev;
258+
priv->sub_master->master_cb = binder->cd_fan_list_cb;
259+
lwmi_cd_call_master_cb(priv);
260+
166261
break;
167262
case LENOVO_CAPABILITY_DATA_01:
168263
binder->cd01_list = priv->list;
@@ -174,8 +269,168 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
174269
return 0;
175270
}
176271

272+
/**
273+
* lwmi_cd_component_unbind() - Unbind component to master device.
274+
* @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
275+
* @om_dev: Pointer to the lenovo-wmi-other driver parent device.
276+
* @data: Unused.
277+
*
278+
* If cd_dev is a sub-master, clear the collected data from the master device to
279+
* prevent the binding establishment between the sub-master and the sub-
280+
* component (if it's about to happen) from calling the master callback.
281+
*/
282+
static void lwmi_cd_component_unbind(struct device *cd_dev,
283+
struct device *om_dev, void *data)
284+
{
285+
struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
286+
287+
switch (priv->list->type) {
288+
case LENOVO_CAPABILITY_DATA_00:
289+
priv->sub_master->master_dev = NULL;
290+
priv->sub_master->master_cb = NULL;
291+
return;
292+
default:
293+
return;
294+
}
295+
}
296+
177297
static const struct component_ops lwmi_cd_component_ops = {
178298
.bind = lwmi_cd_component_bind,
299+
.unbind = lwmi_cd_component_unbind,
300+
};
301+
302+
/**
303+
* lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
304+
* @dev: The sub-master capdata basic device.
305+
*
306+
* Call component_bind_all to bind the sub-component device to the sub-master
307+
* device. On success, collect the pointer to the sub-component list and try
308+
* to call the master callback.
309+
*
310+
* Return: 0 on success, or an error code.
311+
*/
312+
static int lwmi_cd_sub_master_bind(struct device *dev)
313+
{
314+
struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
315+
struct cd_list *sub_component_list;
316+
int ret;
317+
318+
ret = component_bind_all(dev, &sub_component_list);
319+
if (ret)
320+
return ret;
321+
322+
priv->sub_master->sub_component_list = sub_component_list;
323+
lwmi_cd_call_master_cb(priv);
324+
325+
return 0;
326+
}
327+
328+
/**
329+
* lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
330+
* @dev: The sub-master capdata basic device
331+
*
332+
* Clear the collected pointer to the sub-component list to prevent the binding
333+
* establishment between the sub-master and the sub-component (if it's about to
334+
* happen) from calling the master callback. Then, call component_unbind_all to
335+
* unbind the sub-component device from the sub-master device.
336+
*/
337+
static void lwmi_cd_sub_master_unbind(struct device *dev)
338+
{
339+
struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
340+
341+
priv->sub_master->sub_component_list = NULL;
342+
343+
component_unbind_all(dev, NULL);
344+
}
345+
346+
static const struct component_master_ops lwmi_cd_sub_master_ops = {
347+
.bind = lwmi_cd_sub_master_bind,
348+
.unbind = lwmi_cd_sub_master_unbind,
349+
};
350+
351+
/**
352+
* lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
353+
* @priv: Pointer to the sub-master capdata device private data.
354+
* @sub_component_type: Type of the sub-component.
355+
*
356+
* Match the sub-component type and register the current capdata device as a
357+
* sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
358+
* component as non-existent without registering sub-master.
359+
*
360+
* Return: 0 on success, or an error code.
361+
*/
362+
static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
363+
enum lwmi_cd_type sub_component_type)
364+
{
365+
struct component_match *master_match = NULL;
366+
int ret;
367+
368+
priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
369+
if (!priv->sub_master)
370+
return -ENOMEM;
371+
372+
if (sub_component_type == CD_TYPE_NONE) {
373+
/* The master callback will be called with NULL on bind. */
374+
priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
375+
priv->sub_master->registered = false;
376+
return 0;
377+
}
378+
379+
/*
380+
* lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
381+
* data cannot be used here. Steal one from lwmi_cd_table.
382+
*/
383+
component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
384+
(void *)&lwmi_cd_table[sub_component_type].type);
385+
if (IS_ERR(master_match))
386+
return PTR_ERR(master_match);
387+
388+
ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
389+
master_match);
390+
if (ret)
391+
return ret;
392+
393+
priv->sub_master->registered = true;
394+
return 0;
395+
}
396+
397+
/**
398+
* lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
399+
* @priv: Pointer to the sub-master capdata device private data.
400+
*/
401+
static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
402+
{
403+
if (!priv->sub_master->registered)
404+
return;
405+
406+
component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
407+
priv->sub_master->registered = false;
408+
}
409+
410+
/**
411+
* lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
412+
* @sc_dev: Pointer to the sub-component capdata parent device.
413+
* @sm_dev: Pointer to the sub-master capdata parent device.
414+
* @data: Pointer used to return the capability data list pointer.
415+
*
416+
* On sub-master's bind, provide a pointer to the local capdata list.
417+
* This is used by the sub-master to call the master callback.
418+
*
419+
* Return: 0
420+
*/
421+
static int lwmi_cd_sub_component_bind(struct device *sc_dev,
422+
struct device *sm_dev, void *data)
423+
{
424+
struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
425+
struct cd_list **listp = data;
426+
427+
*listp = priv->list;
428+
429+
return 0;
430+
}
431+
432+
static const struct component_ops lwmi_cd_sub_component_ops = {
433+
.bind = lwmi_cd_sub_component_bind,
179434
};
180435

181436
/*
@@ -471,9 +726,28 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
471726
goto out;
472727

473728
switch (info->type) {
474-
case LENOVO_CAPABILITY_DATA_00:
729+
case LENOVO_CAPABILITY_DATA_00: {
730+
enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
731+
struct capdata00 capdata00;
732+
733+
ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
734+
if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
735+
dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
736+
sub_component_type = CD_TYPE_NONE;
737+
}
738+
739+
/* Sub-master (capdata00) <-> sub-component (capdata_fan) */
740+
ret = lwmi_cd_sub_master_add(priv, sub_component_type);
741+
if (ret)
742+
goto out;
743+
744+
/* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
475745
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
746+
if (ret)
747+
lwmi_cd_sub_master_del(priv);
748+
476749
goto out;
750+
}
477751
case LENOVO_CAPABILITY_DATA_01:
478752
priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
479753

@@ -489,6 +763,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
489763
ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
490764
goto out;
491765
case LENOVO_FAN_TEST_DATA:
766+
ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
492767
goto out;
493768
default:
494769
return -EINVAL;
@@ -510,10 +785,13 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
510785

511786
switch (priv->list->type) {
512787
case LENOVO_CAPABILITY_DATA_00:
788+
lwmi_cd_sub_master_del(priv);
789+
fallthrough;
513790
case LENOVO_CAPABILITY_DATA_01:
514791
component_del(&wdev->dev, &lwmi_cd_component_ops);
515792
break;
516793
case LENOVO_FAN_TEST_DATA:
794+
component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
517795
break;
518796
default:
519797
WARN_ON(1);

0 commit comments

Comments
 (0)