Skip to content

Commit a5c2fcd

Browse files
Wer-Wolfrafaeljw
authored andcommitted
ACPI: fan: Add support for Microsoft fan extensions
Microsoft has designed a set of extensions for the ACPI fan device allowing the OS to specify a set of fan speed trip points. The platform firmware will then notify the ACPI fan device when one of the trip points is triggered. Unfortunatly, some device manufacturers (like HP) blindly assume that the OS will use said extensions and thus only update the values returned by the _FST control method when receiving such a notification. As a result, the ACPI fan driver is currently unusable on such machines, always reporting a constant value. Fix this by adding support for the Microsoft extensions. During probe and when resuming from suspend, the driver will attempt to trigger an initial notification that will update the values returned by _FST. Said trip points will be updated each time a notification is received from the platform firmware to ensure that the values returned by the _FST control method are updated. Link: https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide Closes: lm-sensors/lm-sensors#506 Signed-off-by: Armin Wolf <W_Armin@gmx.de> [ rjw: Edits of the new code comments ] Link: https://patch.msgid.link/20251024183824.5656-4-W_Armin@gmx.de Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
1 parent 3d4ca76 commit a5c2fcd

2 files changed

Lines changed: 174 additions & 2 deletions

File tree

drivers/acpi/fan.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ struct acpi_fan {
5656
struct acpi_fan_fif fif;
5757
struct acpi_fan_fps *fps;
5858
int fps_count;
59+
/* A value of 0 means that trippoint-related functions are not supported */
60+
u32 fan_trip_granularity;
5961
#if IS_REACHABLE(CONFIG_HWMON)
6062
struct device *hdev;
6163
#endif

drivers/acpi/fan_core.c

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
* Copyright (C) 2022 Intel Corporation. All rights reserved.
88
*/
99

10+
#include <linux/bits.h>
1011
#include <linux/kernel.h>
12+
#include <linux/limits.h>
13+
#include <linux/math.h>
14+
#include <linux/math64.h>
1115
#include <linux/module.h>
1216
#include <linux/init.h>
1317
#include <linux/types.h>
1418
#include <linux/uaccess.h>
19+
#include <linux/uuid.h>
1520
#include <linux/thermal.h>
1621
#include <linux/acpi.h>
1722
#include <linux/platform_device.h>
@@ -21,6 +26,24 @@
2126

2227
#define ACPI_FAN_NOTIFY_STATE_CHANGED 0x80
2328

29+
/*
30+
* Defined inside the "Fan Noise Signal" section at
31+
* https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide.
32+
*/
33+
static const guid_t acpi_fan_microsoft_guid = GUID_INIT(0xA7611840, 0x99FE, 0x41AE, 0xA4, 0x88,
34+
0x35, 0xC7, 0x59, 0x26, 0xC8, 0xEB);
35+
#define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1
36+
#define ACPI_FAN_DSM_SET_TRIP_POINTS 2
37+
#define ACPI_FAN_DSM_GET_OPERATING_RANGES 3
38+
39+
/*
40+
* Ensures that fans with a very low trip point granularity
41+
* do not send too many notifications.
42+
*/
43+
static uint min_trip_distance = 100;
44+
module_param(min_trip_distance, uint, 0);
45+
MODULE_PARM_DESC(min_trip_distance, "Minimum distance between fan speed trip points in RPM");
46+
2447
static const struct acpi_device_id fan_device_ids[] = {
2548
ACPI_FAN_DEVICE_IDS,
2649
{"", 0},
@@ -310,6 +333,132 @@ static int acpi_fan_get_fps(struct acpi_device *device)
310333
return status;
311334
}
312335

336+
static int acpi_fan_dsm_init(struct device *dev)
337+
{
338+
union acpi_object dummy = {
339+
.package = {
340+
.type = ACPI_TYPE_PACKAGE,
341+
.count = 0,
342+
.elements = NULL,
343+
},
344+
};
345+
struct acpi_fan *fan = dev_get_drvdata(dev);
346+
union acpi_object *obj;
347+
int ret = 0;
348+
349+
if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
350+
BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) |
351+
BIT(ACPI_FAN_DSM_SET_TRIP_POINTS)))
352+
return 0;
353+
354+
dev_info(dev, "Using Microsoft fan extensions\n");
355+
356+
obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0,
357+
ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy,
358+
ACPI_TYPE_INTEGER);
359+
if (!obj)
360+
return -EIO;
361+
362+
if (obj->integer.value > U32_MAX)
363+
ret = -EOVERFLOW;
364+
else
365+
fan->fan_trip_granularity = obj->integer.value;
366+
367+
kfree(obj);
368+
369+
return ret;
370+
}
371+
372+
static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower)
373+
{
374+
union acpi_object args[2] = {
375+
{
376+
.integer = {
377+
.type = ACPI_TYPE_INTEGER,
378+
.value = lower,
379+
},
380+
},
381+
{
382+
.integer = {
383+
.type = ACPI_TYPE_INTEGER,
384+
.value = upper,
385+
},
386+
},
387+
};
388+
struct acpi_fan *fan = dev_get_drvdata(dev);
389+
union acpi_object in = {
390+
.package = {
391+
.type = ACPI_TYPE_PACKAGE,
392+
.count = ARRAY_SIZE(args),
393+
.elements = args,
394+
},
395+
};
396+
union acpi_object *obj;
397+
398+
obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
399+
ACPI_FAN_DSM_SET_TRIP_POINTS, &in);
400+
kfree(obj);
401+
402+
return 0;
403+
}
404+
405+
static int acpi_fan_dsm_start(struct device *dev)
406+
{
407+
struct acpi_fan *fan = dev_get_drvdata(dev);
408+
int ret;
409+
410+
if (!fan->fan_trip_granularity)
411+
return 0;
412+
413+
/*
414+
* Some firmware implementations only update the values returned by the
415+
* _FST control method when a notification is received. This usually
416+
* works with Microsoft Windows as setting up trip points will keep
417+
* triggering said notifications, but will cause issues when using _FST
418+
* without the Microsoft-specific trip point extension.
419+
*
420+
* Because of this, an initial notification needs to be triggered to
421+
* start the cycle of trip points updates. This is achieved by setting
422+
* the trip points sequencially to two separate ranges. As by the
423+
* Microsoft specification the firmware should trigger a notification
424+
* immediately if the fan speed is outside the trip point range. This
425+
* _should_ result in at least one notification as both ranges do not
426+
* overlap, meaning that the current fan speed needs to be outside at
427+
* least one range.
428+
*/
429+
ret = acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity, 0);
430+
if (ret < 0)
431+
return ret;
432+
433+
return acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity * 3,
434+
fan->fan_trip_granularity * 2);
435+
}
436+
437+
static int acpi_fan_dsm_update_trips_points(struct device *dev, struct acpi_fan_fst *fst)
438+
{
439+
struct acpi_fan *fan = dev_get_drvdata(dev);
440+
u64 upper, lower;
441+
442+
if (!fan->fan_trip_granularity)
443+
return 0;
444+
445+
if (!acpi_fan_speed_valid(fst->speed))
446+
return -EINVAL;
447+
448+
upper = roundup_u64(fst->speed + min_trip_distance, fan->fan_trip_granularity);
449+
if (fst->speed <= min_trip_distance) {
450+
lower = 0;
451+
} else {
452+
/*
453+
* Valid fan speed values cannot be larger than 32 bit, so
454+
* we can safely assume that no overflow will happen here.
455+
*/
456+
lower = rounddown((u32)fst->speed - min_trip_distance, fan->fan_trip_granularity);
457+
}
458+
459+
return acpi_fan_dsm_set_trip_points(dev, upper, lower);
460+
}
461+
313462
static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context)
314463
{
315464
struct device *dev = context;
@@ -323,8 +472,13 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context
323472
* receive an ACPI event indicating that the fan state has changed.
324473
*/
325474
ret = acpi_fan_get_fst(handle, &fst);
326-
if (ret < 0)
475+
if (ret < 0) {
327476
dev_err(dev, "Error retrieving current fan status: %d\n", ret);
477+
} else {
478+
ret = acpi_fan_dsm_update_trips_points(dev, &fst);
479+
if (ret < 0)
480+
dev_err(dev, "Failed to update trip points: %d\n", ret);
481+
}
328482

329483
acpi_fan_notify_hwmon(dev);
330484
acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
@@ -394,6 +548,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
394548
}
395549

396550
if (fan->has_fst) {
551+
result = acpi_fan_dsm_init(&pdev->dev);
552+
if (result)
553+
return result;
554+
397555
result = devm_acpi_fan_create_hwmon(&pdev->dev);
398556
if (result)
399557
return result;
@@ -402,6 +560,12 @@ static int acpi_fan_probe(struct platform_device *pdev)
402560
if (result)
403561
return result;
404562

563+
result = acpi_fan_dsm_start(&pdev->dev);
564+
if (result) {
565+
dev_err(&pdev->dev, "Failed to start Microsoft fan extensions\n");
566+
return result;
567+
}
568+
405569
result = acpi_fan_create_attributes(device);
406570
if (result)
407571
return result;
@@ -487,8 +651,14 @@ static int acpi_fan_suspend(struct device *dev)
487651

488652
static int acpi_fan_resume(struct device *dev)
489653
{
490-
int result;
491654
struct acpi_fan *fan = dev_get_drvdata(dev);
655+
int result;
656+
657+
if (fan->has_fst) {
658+
result = acpi_fan_dsm_start(dev);
659+
if (result)
660+
dev_err(dev, "Failed to start Microsoft fan extensions: %d\n", result);
661+
}
492662

493663
if (fan->acpi4)
494664
return 0;

0 commit comments

Comments
 (0)