1313 * attribute has multiple pages, one for each of the thermal modes managed by
1414 * the Gamezone interface.
1515 *
16+ * Fan Test Data includes the max/min fan speed RPM for each fan. This is
17+ * reference data for self-test. If the fan is in good condition, it is capable
18+ * to spin faster than max RPM or slower than min RPM.
19+ *
1620 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
1721 * - Initial implementation (formerly named lenovo-wmi-capdata01)
1822 *
3236#include <linux/err.h>
3337#include <linux/export.h>
3438#include <linux/gfp_types.h>
39+ #include <linux/limits.h>
3540#include <linux/module.h>
3641#include <linux/mutex.h>
3742#include <linux/mutex_types.h>
4550
4651#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
4752#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
53+ #define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
4854
4955#define ACPI_AC_CLASS "ac_adapter"
5056#define ACPI_AC_NOTIFY_STATUS 0x80
5157
5258enum lwmi_cd_type {
5359 LENOVO_CAPABILITY_DATA_00 ,
5460 LENOVO_CAPABILITY_DATA_01 ,
61+ LENOVO_FAN_TEST_DATA ,
5562};
5663
5764#define LWMI_CD_TABLE_ITEM (_type ) \
@@ -66,6 +73,7 @@ static const struct lwmi_cd_info {
6673} lwmi_cd_table [] = {
6774 LWMI_CD_TABLE_ITEM (LENOVO_CAPABILITY_DATA_00 ),
6875 LWMI_CD_TABLE_ITEM (LENOVO_CAPABILITY_DATA_01 ),
76+ LWMI_CD_TABLE_ITEM (LENOVO_FAN_TEST_DATA ),
6977};
7078
7179struct lwmi_cd_priv {
@@ -82,6 +90,7 @@ struct cd_list {
8290 union {
8391 DECLARE_FLEX_ARRAY (struct capdata00 , cd00 );
8492 DECLARE_FLEX_ARRAY (struct capdata01 , cd01 );
93+ DECLARE_FLEX_ARRAY (struct capdata_fan , cd_fan );
8594 };
8695};
8796
@@ -121,6 +130,10 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match
121130 return ;
122131
123132 for (i = 0 ; i < ARRAY_SIZE (lwmi_cd_table ); i ++ ) {
133+ /* Skip sub-components. */
134+ if (lwmi_cd_table [i ].type == LENOVO_FAN_TEST_DATA )
135+ continue ;
136+
124137 component_match_add (master , matchptr , lwmi_cd_match ,
125138 (void * )& lwmi_cd_table [i ].type );
126139 if (IS_ERR (* matchptr ))
@@ -200,6 +213,9 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
200213DEF_LWMI_CDXX_GET_DATA (cd01 , LENOVO_CAPABILITY_DATA_01 , struct capdata01 );
201214EXPORT_SYMBOL_NS_GPL (lwmi_cd01_get_data , "LENOVO_WMI_CAPDATA" );
202215
216+ DEF_LWMI_CDXX_GET_DATA (cd_fan , LENOVO_FAN_TEST_DATA , struct capdata_fan );
217+ EXPORT_SYMBOL_NS_GPL (lwmi_cd_fan_get_data , "LENOVO_WMI_CAPDATA" );
218+
203219/**
204220 * lwmi_cd_cache() - Cache all WMI data block information
205221 * @priv: lenovo-wmi-capdata driver data.
@@ -223,6 +239,9 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
223239 p = & priv -> list -> cd01 [0 ];
224240 size = sizeof (priv -> list -> cd01 [0 ]);
225241 break ;
242+ case LENOVO_FAN_TEST_DATA :
243+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
244+ return 0 ;
226245 default :
227246 return - EINVAL ;
228247 }
@@ -245,6 +264,72 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
245264 return 0 ;
246265}
247266
267+ /**
268+ * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
269+ * @priv: lenovo-wmi-capdata driver data.
270+ * @listptr: Pointer to returned cd_list pointer.
271+ *
272+ * Return: count of fans found, or an error.
273+ */
274+ static int lwmi_cd_fan_list_alloc_cache (struct lwmi_cd_priv * priv , struct cd_list * * listptr )
275+ {
276+ struct cd_list * list ;
277+ size_t size ;
278+ u32 count ;
279+ int idx ;
280+
281+ /* Emit unaligned access to u8 buffer with __packed. */
282+ struct cd_fan_block {
283+ u32 nr ;
284+ u32 data []; /* id[nr], max_rpm[nr], min_rpm[nr] */
285+ } __packed * block ;
286+
287+ union acpi_object * ret_obj __free (kfree ) = wmidev_block_query (priv -> wdev , 0 );
288+ if (!ret_obj )
289+ return - ENODEV ;
290+
291+ if (ret_obj -> type == ACPI_TYPE_BUFFER ) {
292+ block = (struct cd_fan_block * )ret_obj -> buffer .pointer ;
293+ size = ret_obj -> buffer .length ;
294+
295+ count = size >= sizeof (* block ) ? block -> nr : 0 ;
296+ if (size < struct_size (block , data , count * 3 )) {
297+ dev_warn (& priv -> wdev -> dev ,
298+ "incomplete fan test data block: %zu < %zu, ignoring\n" ,
299+ size , struct_size (block , data , count * 3 ));
300+ count = 0 ;
301+ } else if (count > U8_MAX ) {
302+ dev_warn (& priv -> wdev -> dev ,
303+ "too many fans reported: %u > %u, truncating\n" ,
304+ count , U8_MAX );
305+ count = U8_MAX ;
306+ }
307+ } else {
308+ /*
309+ * This is usually caused by a dummy ACPI method. Do not return an error
310+ * as failing to probe this device will result in sub-master device being
311+ * unbound. This behavior aligns with lwmi_cd_cache().
312+ */
313+ count = 0 ;
314+ }
315+
316+ list = devm_kzalloc (& priv -> wdev -> dev , struct_size (list , cd_fan , count ), GFP_KERNEL );
317+ if (!list )
318+ return - ENOMEM ;
319+
320+ for (idx = 0 ; idx < count ; idx ++ ) {
321+ /* Do not calculate array index using count, as it may be truncated. */
322+ list -> cd_fan [idx ] = (struct capdata_fan ) {
323+ .id = block -> data [idx ],
324+ .max_rpm = block -> data [idx + block -> nr ],
325+ .min_rpm = block -> data [idx + (2 * block -> nr )],
326+ };
327+ }
328+
329+ * listptr = list ;
330+ return count ;
331+ }
332+
248333/**
249334 * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
250335 * @priv: lenovo-wmi-capdata driver data.
@@ -270,6 +355,12 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
270355 case LENOVO_CAPABILITY_DATA_01 :
271356 list_size = struct_size (list , cd01 , count );
272357 break ;
358+ case LENOVO_FAN_TEST_DATA :
359+ count = lwmi_cd_fan_list_alloc_cache (priv , & list );
360+ if (count < 0 )
361+ return count ;
362+
363+ goto got_list ;
273364 default :
274365 return - EINVAL ;
275366 }
@@ -278,6 +369,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
278369 if (!list )
279370 return - ENOMEM ;
280371
372+ got_list :
281373 ret = devm_mutex_init (& priv -> wdev -> dev , & list -> list_mutex );
282374 if (ret )
283375 return ret ;
@@ -396,6 +488,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
396488
397489 ret = component_add (& wdev -> dev , & lwmi_cd_component_ops );
398490 goto out ;
491+ case LENOVO_FAN_TEST_DATA :
492+ goto out ;
399493 default :
400494 return - EINVAL ;
401495 }
@@ -419,6 +513,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
419513 case LENOVO_CAPABILITY_DATA_01 :
420514 component_del (& wdev -> dev , & lwmi_cd_component_ops );
421515 break ;
516+ case LENOVO_FAN_TEST_DATA :
517+ break ;
422518 default :
423519 WARN_ON (1 );
424520 }
@@ -431,6 +527,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
431527static const struct wmi_device_id lwmi_cd_id_table [] = {
432528 { LWMI_CD_WDEV_ID (LENOVO_CAPABILITY_DATA_00 ) },
433529 { LWMI_CD_WDEV_ID (LENOVO_CAPABILITY_DATA_01 ) },
530+ { LWMI_CD_WDEV_ID (LENOVO_FAN_TEST_DATA ) },
434531 {}
435532};
436533
0 commit comments