* [PATCH v3 1/3] ACPI: fan: Add basic notification support
2025-10-24 18:38 [PATCH v3 0/3] ACPI fan _DSM support Armin Wolf
@ 2025-10-24 18:38 ` Armin Wolf
2025-10-24 18:38 ` [PATCH v3 2/3] ACPI: fan: Add hwmon " Armin Wolf
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Armin Wolf @ 2025-10-24 18:38 UTC (permalink / raw)
To: rafael, lenb; +Cc: linux-acpi, linux-kernel
The ACPI specification states that the platform firmware can notify
the ACPI fan device that the fan speed has changed an that the _FST
control method should be reevaluated. Add support for this mechanism
to prepare for future changes.
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
drivers/acpi/fan_core.c | 50 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c
index 46e7fe7a506d..9ee4ef2d6dbc 100644
--- a/drivers/acpi/fan_core.c
+++ b/drivers/acpi/fan_core.c
@@ -19,6 +19,8 @@
#include "fan.h"
+#define ACPI_FAN_NOTIFY_STATE_CHANGED 0x80
+
static const struct acpi_device_id fan_device_ids[] = {
ACPI_FAN_DEVICE_IDS,
{"", 0},
@@ -308,6 +310,50 @@ static int acpi_fan_get_fps(struct acpi_device *device)
return status;
}
+static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context)
+{
+ struct device *dev = context;
+ struct acpi_fan_fst fst;
+ int ret;
+
+ switch (event) {
+ case ACPI_FAN_NOTIFY_STATE_CHANGED:
+ /*
+ * The ACPI specification says that we must evaluate _FST when we
+ * receive an ACPI event indicating that the fan state has changed.
+ */
+ ret = acpi_fan_get_fst(handle, &fst);
+ if (ret < 0)
+ dev_err(dev, "Error retrieving current fan status: %d\n", ret);
+
+ acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
+ break;
+ default:
+ dev_dbg(dev, "Unsupported ACPI notification 0x%x\n", event);
+ break;
+ }
+}
+
+static void acpi_fan_notify_remove(void *data)
+{
+ struct acpi_fan *fan = data;
+
+ acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler);
+}
+
+static int devm_acpi_fan_notify_init(struct device *dev)
+{
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+ acpi_status status;
+
+ status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY,
+ acpi_fan_notify_handler, dev);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan);
+}
+
static int acpi_fan_probe(struct platform_device *pdev)
{
int result = 0;
@@ -351,6 +397,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
if (result)
return result;
+ result = devm_acpi_fan_notify_init(&pdev->dev);
+ if (result)
+ return result;
+
result = acpi_fan_create_attributes(device);
if (result)
return result;
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v3 2/3] ACPI: fan: Add hwmon notification support
2025-10-24 18:38 [PATCH v3 0/3] ACPI fan _DSM support Armin Wolf
2025-10-24 18:38 ` [PATCH v3 1/3] ACPI: fan: Add basic notification support Armin Wolf
@ 2025-10-24 18:38 ` Armin Wolf
2025-10-24 18:38 ` [PATCH v3 3/3] ACPI: fan: Add support for Microsoft fan extensions Armin Wolf
2025-10-27 19:58 ` [PATCH v3 0/3] ACPI fan _DSM support Rafael J. Wysocki
3 siblings, 0 replies; 6+ messages in thread
From: Armin Wolf @ 2025-10-24 18:38 UTC (permalink / raw)
To: rafael, lenb; +Cc: linux-acpi, linux-kernel
The platform firmware can notify the ACPI fan device that the fan
speed has changed. Relay this notification to the hwmon device if
present so that userspace applications can react to it.
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
drivers/acpi/fan.h | 5 +++++
drivers/acpi/fan_core.c | 1 +
drivers/acpi/fan_hwmon.c | 15 +++++++++++----
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h
index 0d73433c3889..dcc1ad3118ff 100644
--- a/drivers/acpi/fan.h
+++ b/drivers/acpi/fan.h
@@ -56,6 +56,9 @@ struct acpi_fan {
struct acpi_fan_fif fif;
struct acpi_fan_fps *fps;
int fps_count;
+#if IS_REACHABLE(CONFIG_HWMON)
+ struct device *hdev;
+#endif
struct thermal_cooling_device *cdev;
struct device_attribute fst_speed;
struct device_attribute fine_grain_control;
@@ -99,8 +102,10 @@ void acpi_fan_delete_attributes(struct acpi_device *device);
#if IS_REACHABLE(CONFIG_HWMON)
int devm_acpi_fan_create_hwmon(struct device *dev);
+void acpi_fan_notify_hwmon(struct device *dev);
#else
static inline int devm_acpi_fan_create_hwmon(struct device *dev) { return 0; };
+static inline void acpi_fan_notify_hwmon(struct device *dev) { };
#endif
#endif
diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c
index 9ee4ef2d6dbc..7be22c52670c 100644
--- a/drivers/acpi/fan_core.c
+++ b/drivers/acpi/fan_core.c
@@ -326,6 +326,7 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context
if (ret < 0)
dev_err(dev, "Error retrieving current fan status: %d\n", ret);
+ acpi_fan_notify_hwmon(dev);
acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
break;
default:
diff --git a/drivers/acpi/fan_hwmon.c b/drivers/acpi/fan_hwmon.c
index 47a02ef5a606..d3374f8f524b 100644
--- a/drivers/acpi/fan_hwmon.c
+++ b/drivers/acpi/fan_hwmon.c
@@ -162,12 +162,19 @@ static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = {
.info = acpi_fan_hwmon_info,
};
+void acpi_fan_notify_hwmon(struct device *dev)
+{
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+
+ hwmon_notify_event(fan->hdev, hwmon_fan, hwmon_fan_input, 0);
+}
+
int devm_acpi_fan_create_hwmon(struct device *dev)
{
struct acpi_fan *fan = dev_get_drvdata(dev);
- struct device *hdev;
- hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan, &acpi_fan_hwmon_chip_info,
- NULL);
- return PTR_ERR_OR_ZERO(hdev);
+ fan->hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan,
+ &acpi_fan_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(fan->hdev);
}
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v3 3/3] ACPI: fan: Add support for Microsoft fan extensions
2025-10-24 18:38 [PATCH v3 0/3] ACPI fan _DSM support Armin Wolf
2025-10-24 18:38 ` [PATCH v3 1/3] ACPI: fan: Add basic notification support Armin Wolf
2025-10-24 18:38 ` [PATCH v3 2/3] ACPI: fan: Add hwmon " Armin Wolf
@ 2025-10-24 18:38 ` Armin Wolf
2025-10-27 19:58 ` [PATCH v3 0/3] ACPI fan _DSM support Rafael J. Wysocki
3 siblings, 0 replies; 6+ messages in thread
From: Armin Wolf @ 2025-10-24 18:38 UTC (permalink / raw)
To: rafael, lenb; +Cc: linux-acpi, linux-kernel
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: https://github.com/lm-sensors/lm-sensors/issues/506
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
drivers/acpi/fan.h | 2 +
drivers/acpi/fan_core.c | 173 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 173 insertions(+), 2 deletions(-)
diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h
index dcc1ad3118ff..f85f9a0fbfcd 100644
--- a/drivers/acpi/fan.h
+++ b/drivers/acpi/fan.h
@@ -56,6 +56,8 @@ struct acpi_fan {
struct acpi_fan_fif fif;
struct acpi_fan_fps *fps;
int fps_count;
+ /* A value of 0 means that trippoint-related functions are not supported */
+ u32 fan_trip_granularity;
#if IS_REACHABLE(CONFIG_HWMON)
struct device *hdev;
#endif
diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c
index 7be22c52670c..1ec213afcdfd 100644
--- a/drivers/acpi/fan_core.c
+++ b/drivers/acpi/fan_core.c
@@ -7,11 +7,16 @@
* Copyright (C) 2022 Intel Corporation. All rights reserved.
*/
+#include <linux/bits.h>
#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/math.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/uaccess.h>
+#include <linux/uuid.h>
#include <linux/thermal.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
@@ -21,6 +26,24 @@
#define ACPI_FAN_NOTIFY_STATE_CHANGED 0x80
+/*
+ * Defined inside the "Fan Noise Signal" section at
+ * https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide.
+ */
+static const guid_t acpi_fan_microsoft_guid = GUID_INIT(0xA7611840, 0x99FE, 0x41AE, 0xA4, 0x88,
+ 0x35, 0xC7, 0x59, 0x26, 0xC8, 0xEB);
+#define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1
+#define ACPI_FAN_DSM_SET_TRIP_POINTS 2
+#define ACPI_FAN_DSM_GET_OPERATING_RANGES 3
+
+/*
+ * Ensures that fans with a very low trip point granularity
+ * do not send too many notifications.
+ */
+static uint min_trip_distance = 100;
+module_param(min_trip_distance, uint, 0);
+MODULE_PARM_DESC(min_trip_distance, "Minimum distance between fan speed trip points in RPM");
+
static const struct acpi_device_id fan_device_ids[] = {
ACPI_FAN_DEVICE_IDS,
{"", 0},
@@ -310,6 +333,131 @@ static int acpi_fan_get_fps(struct acpi_device *device)
return status;
}
+static int acpi_fan_dsm_init(struct device *dev)
+{
+ union acpi_object dummy = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = 0,
+ .elements = NULL,
+ },
+ };
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+ union acpi_object *obj;
+ int ret = 0;
+
+ if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
+ BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) |
+ BIT(ACPI_FAN_DSM_SET_TRIP_POINTS)))
+ return 0;
+
+ dev_info(dev, "Using Microsoft fan extensions\n");
+
+ obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0,
+ ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy,
+ ACPI_TYPE_INTEGER);
+ if (!obj)
+ return -EIO;
+
+ if (obj->integer.value > U32_MAX)
+ ret = -EOVERFLOW;
+ else
+ fan->fan_trip_granularity = obj->integer.value;
+
+ kfree(obj);
+
+ return ret;
+}
+
+static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower)
+{
+ union acpi_object args[2] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = lower,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = upper,
+ },
+ },
+ };
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+ union acpi_object in = {
+ .package = {
+ .type = ACPI_TYPE_PACKAGE,
+ .count = ARRAY_SIZE(args),
+ .elements = args,
+ },
+ };
+ union acpi_object *obj;
+
+ obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
+ ACPI_FAN_DSM_SET_TRIP_POINTS, &in);
+ kfree(obj);
+
+ return 0;
+}
+
+static int acpi_fan_dsm_start(struct device *dev)
+{
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+ int ret;
+
+ if (!fan->fan_trip_granularity)
+ return 0;
+
+ /*
+ * Some firmware implementations only update the values returned by the
+ * _FST control method when a notification is received. This usually works
+ * with Microsoft Windows as setting up trip points will keep triggering
+ * said notifications, but will cause issues when using _FST without the
+ * Microsoft-specific trip point extension.
+ *
+ * Because of this we have to ensure that an initial notification is triggered
+ * to start the cycle of trip points updates. We achive this by setting the trip
+ * points sequencially to two separate ranges. As by the Microsoft specification
+ * the firmware should trigger a notification immediately if the fan speed is outside
+ * of the trip point range. This _should_ result in at least one notification as both
+ * ranges do not overlap, meaning that the current fan speed needs to be outside of
+ * at least one range.
+ */
+ ret = acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity, 0);
+ if (ret < 0)
+ return ret;
+
+ return acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity * 3,
+ fan->fan_trip_granularity * 2);
+}
+
+static int acpi_fan_dsm_update_trips_points(struct device *dev, struct acpi_fan_fst *fst)
+{
+ struct acpi_fan *fan = dev_get_drvdata(dev);
+ u64 upper, lower;
+
+ if (!fan->fan_trip_granularity)
+ return 0;
+
+ if (!acpi_fan_speed_valid(fst->speed))
+ return -EINVAL;
+
+ upper = roundup_u64(fst->speed + min_trip_distance, fan->fan_trip_granularity);
+ if (fst->speed <= min_trip_distance) {
+ lower = 0;
+ } else {
+ /*
+ * Valid fan speed values cannot be larger than 32 bit, so
+ * we can safely assume that no overflow will happen here.
+ */
+ lower = rounddown((u32)fst->speed - min_trip_distance, fan->fan_trip_granularity);
+ }
+
+ return acpi_fan_dsm_set_trip_points(dev, upper, lower);
+}
+
static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context)
{
struct device *dev = context;
@@ -323,8 +471,13 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context
* receive an ACPI event indicating that the fan state has changed.
*/
ret = acpi_fan_get_fst(handle, &fst);
- if (ret < 0)
+ if (ret < 0) {
dev_err(dev, "Error retrieving current fan status: %d\n", ret);
+ } else {
+ ret = acpi_fan_dsm_update_trips_points(dev, &fst);
+ if (ret < 0)
+ dev_err(dev, "Failed to update trip points: %d\n", ret);
+ }
acpi_fan_notify_hwmon(dev);
acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
@@ -394,6 +547,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
}
if (fan->has_fst) {
+ result = acpi_fan_dsm_init(&pdev->dev);
+ if (result)
+ return result;
+
result = devm_acpi_fan_create_hwmon(&pdev->dev);
if (result)
return result;
@@ -402,6 +559,12 @@ static int acpi_fan_probe(struct platform_device *pdev)
if (result)
return result;
+ result = acpi_fan_dsm_start(&pdev->dev);
+ if (result) {
+ dev_err(&pdev->dev, "Failed to start Microsoft fan extensions\n");
+ return result;
+ }
+
result = acpi_fan_create_attributes(device);
if (result)
return result;
@@ -487,8 +650,14 @@ static int acpi_fan_suspend(struct device *dev)
static int acpi_fan_resume(struct device *dev)
{
- int result;
struct acpi_fan *fan = dev_get_drvdata(dev);
+ int result;
+
+ if (fan->has_fst) {
+ result = acpi_fan_dsm_start(dev);
+ if (result)
+ dev_err(dev, "Failed to start Microsoft fan extensions: %d\n", result);
+ }
if (fan->acpi4)
return 0;
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread