Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data

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>
Link: https://patch.msgid.link/20260120182104.163424-6-i@rong.moe
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Rong Zhang and committed by
Ilpo Järvinen
012a8f96 c05f67e6

+121
+17
Documentation/wmi/devices/lenovo-wmi-other.rst
··· 62 62 - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking 63 63 - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking 64 64 65 + LENOVO_FAN_TEST_DATA 66 + ------------------------- 67 + 68 + WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` 69 + 70 + The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of 71 + cooling fans. 65 72 66 73 WMI interface description 67 74 ========================= ··· 121 114 [WmiDataId(2), read, Description("Capability.")] uint32 Capability; 122 115 [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; 123 116 [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[]; 117 + }; 118 + 119 + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")] 120 + class LENOVO_FAN_TEST_DATA { 121 + [key, read] string InstanceName; 122 + [read] boolean Active; 123 + [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans; 124 + [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[]; 125 + [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[]; 126 + [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[]; 124 127 };
+97
drivers/platform/x86/lenovo/wmi-capdata.c
··· 13 13 * attribute has multiple pages, one for each of the thermal modes managed by 14 14 * the Gamezone interface. 15 15 * 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 + * 16 20 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> 17 21 * - Initial implementation (formerly named lenovo-wmi-capdata01) 18 22 * ··· 36 32 #include <linux/err.h> 37 33 #include <linux/export.h> 38 34 #include <linux/gfp_types.h> 35 + #include <linux/limits.h> 39 36 #include <linux/module.h> 40 37 #include <linux/mutex.h> 41 38 #include <linux/mutex_types.h> ··· 50 45 51 46 #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E" 52 47 #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" 48 + #define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" 53 49 54 50 #define ACPI_AC_CLASS "ac_adapter" 55 51 #define ACPI_AC_NOTIFY_STATUS 0x80 ··· 58 52 enum lwmi_cd_type { 59 53 LENOVO_CAPABILITY_DATA_00, 60 54 LENOVO_CAPABILITY_DATA_01, 55 + LENOVO_FAN_TEST_DATA, 61 56 }; 62 57 63 58 #define LWMI_CD_TABLE_ITEM(_type) \ ··· 73 66 } lwmi_cd_table[] = { 74 67 LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), 75 68 LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), 69 + LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA), 76 70 }; 77 71 78 72 struct lwmi_cd_priv { ··· 90 82 union { 91 83 DECLARE_FLEX_ARRAY(struct capdata00, cd00); 92 84 DECLARE_FLEX_ARRAY(struct capdata01, cd01); 85 + DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan); 93 86 }; 94 87 }; 95 88 ··· 130 121 return; 131 122 132 123 for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { 124 + /* Skip sub-components. */ 125 + if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA) 126 + continue; 127 + 133 128 component_match_add(master, matchptr, lwmi_cd_match, 134 129 (void *)&lwmi_cd_table[i].type); 135 130 if (IS_ERR(*matchptr)) ··· 213 200 DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); 214 201 EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); 215 202 203 + DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); 204 + EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); 205 + 216 206 /** 217 207 * lwmi_cd_cache() - Cache all WMI data block information 218 208 * @priv: lenovo-wmi-capdata driver data. ··· 239 223 p = &priv->list->cd01[0]; 240 224 size = sizeof(priv->list->cd01[0]); 241 225 break; 226 + case LENOVO_FAN_TEST_DATA: 227 + /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */ 228 + return 0; 242 229 default: 243 230 return -EINVAL; 244 231 } ··· 262 243 } 263 244 264 245 return 0; 246 + } 247 + 248 + /** 249 + * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list 250 + * @priv: lenovo-wmi-capdata driver data. 251 + * @listptr: Pointer to returned cd_list pointer. 252 + * 253 + * Return: count of fans found, or an error. 254 + */ 255 + static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr) 256 + { 257 + struct cd_list *list; 258 + size_t size; 259 + u32 count; 260 + int idx; 261 + 262 + /* Emit unaligned access to u8 buffer with __packed. */ 263 + struct cd_fan_block { 264 + u32 nr; 265 + u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ 266 + } __packed * block; 267 + 268 + union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0); 269 + if (!ret_obj) 270 + return -ENODEV; 271 + 272 + if (ret_obj->type == ACPI_TYPE_BUFFER) { 273 + block = (struct cd_fan_block *)ret_obj->buffer.pointer; 274 + size = ret_obj->buffer.length; 275 + 276 + count = size >= sizeof(*block) ? block->nr : 0; 277 + if (size < struct_size(block, data, count * 3)) { 278 + dev_warn(&priv->wdev->dev, 279 + "incomplete fan test data block: %zu < %zu, ignoring\n", 280 + size, struct_size(block, data, count * 3)); 281 + count = 0; 282 + } else if (count > U8_MAX) { 283 + dev_warn(&priv->wdev->dev, 284 + "too many fans reported: %u > %u, truncating\n", 285 + count, U8_MAX); 286 + count = U8_MAX; 287 + } 288 + } else { 289 + /* 290 + * This is usually caused by a dummy ACPI method. Do not return an error 291 + * as failing to probe this device will result in sub-master device being 292 + * unbound. This behavior aligns with lwmi_cd_cache(). 293 + */ 294 + count = 0; 295 + } 296 + 297 + list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL); 298 + if (!list) 299 + return -ENOMEM; 300 + 301 + for (idx = 0; idx < count; idx++) { 302 + /* Do not calculate array index using count, as it may be truncated. */ 303 + list->cd_fan[idx] = (struct capdata_fan) { 304 + .id = block->data[idx], 305 + .max_rpm = block->data[idx + block->nr], 306 + .min_rpm = block->data[idx + (2 * block->nr)], 307 + }; 308 + } 309 + 310 + *listptr = list; 311 + return count; 265 312 } 266 313 267 314 /** ··· 355 270 case LENOVO_CAPABILITY_DATA_01: 356 271 list_size = struct_size(list, cd01, count); 357 272 break; 273 + case LENOVO_FAN_TEST_DATA: 274 + count = lwmi_cd_fan_list_alloc_cache(priv, &list); 275 + if (count < 0) 276 + return count; 277 + 278 + goto got_list; 358 279 default: 359 280 return -EINVAL; 360 281 } ··· 369 278 if (!list) 370 279 return -ENOMEM; 371 280 281 + got_list: 372 282 ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); 373 283 if (ret) 374 284 return ret; ··· 488 396 489 397 ret = component_add(&wdev->dev, &lwmi_cd_component_ops); 490 398 goto out; 399 + case LENOVO_FAN_TEST_DATA: 400 + goto out; 491 401 default: 492 402 return -EINVAL; 493 403 } ··· 513 419 case LENOVO_CAPABILITY_DATA_01: 514 420 component_del(&wdev->dev, &lwmi_cd_component_ops); 515 421 break; 422 + case LENOVO_FAN_TEST_DATA: 423 + break; 516 424 default: 517 425 WARN_ON(1); 518 426 } ··· 527 431 static const struct wmi_device_id lwmi_cd_id_table[] = { 528 432 { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, 529 433 { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, 434 + { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) }, 530 435 {} 531 436 }; 532 437
+7
drivers/platform/x86/lenovo/wmi-capdata.h
··· 26 26 u32 max_value; 27 27 }; 28 28 29 + struct capdata_fan { 30 + u32 id; 31 + u32 min_rpm; 32 + u32 max_rpm; 33 + }; 34 + 29 35 struct lwmi_cd_binder { 30 36 struct cd_list *cd00_list; 31 37 struct cd_list *cd01_list; ··· 40 34 void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); 41 35 int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output); 42 36 int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); 37 + int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output); 43 38 44 39 #endif /* !_LENOVO_WMI_CAPDATA_H_ */