[PATCH v4 06/10] ACPI: thermal: Carry out trip point updates under zone lock

From: Rafael J. Wysocki
Date: Fri Aug 04 2023 - 17:26:22 EST


From: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx>

There is a race condition between acpi_thermal_trips_update() and
acpi_thermal_check_fn(), because the trip points may get updated while
the latter is running which in theory may lead to inconsistent results.
For example, if two trips are updated together, using the temperature
value of one of them from before the update and the temperature value
of the other one from after the update may not lead to the expected
outcome.

Moreover, if thermal_get_trend() runs when a trip points update is in
progress, it may end up using stale trip point temperatures.

To address this, make acpi_thermal_trips_update() call
thermal_zone_device_adjust() to carry out the trip points update and
provide a new acpi_thermal_adjust_thermal_zone() wrapper around
__acpi_thermal_trips_update() as the callback function for the latter.

While at it, change the acpi_thermal_trips_update() return data type
to void as that function always returns 0 anyway.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx>
---

v3 -> v4:
* Rework to use thermal_zone_device_adjust() and the .update() callback
instead of using the (exported) zone lock directly.
* Call acpi_queue_thermal_check() from acpi_thermal_trips_update() which
allows code duplication in acpi_thermal_notify() to be reduced.

v2 -> v3: No changes.

v1 -> v2:
* Hold the thermal zone lock instead of thermal_check_lock around trip
point updates (this also helps to protect thermal_get_trend() from using
stale trip temperatures).
* Add a comment documenting the purpose of the locking.
* Make acpi_thermal_trips_update() void.

---
drivers/acpi/thermal.c | 41 ++++++++++++++++++++++++++++-------------
1 file changed, 28 insertions(+), 13 deletions(-)

Index: linux-pm/drivers/acpi/thermal.c
===================================================================
--- linux-pm.orig/drivers/acpi/thermal.c
+++ linux-pm/drivers/acpi/thermal.c
@@ -190,7 +190,7 @@ static int acpi_thermal_get_polling_freq
return 0;
}

-static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag)
+static void __acpi_thermal_trips_update(struct acpi_thermal *tz, int flag)
{
acpi_status status;
unsigned long long tmp;
@@ -398,17 +398,39 @@ static int acpi_thermal_trips_update(str
ACPI_THERMAL_TRIPS_EXCEPTION(flag, tz, "device");
}
}
+}

- return 0;
+static void acpi_thermal_adjust_thermal_zone(struct thermal_zone_device *thermal,
+ unsigned long data)
+{
+ __acpi_thermal_trips_update(thermal_zone_device_priv(thermal), data);
+}
+
+static void acpi_queue_thermal_check(struct acpi_thermal *tz)
+{
+ if (!work_pending(&tz->thermal_check_work))
+ queue_work(acpi_thermal_pm_queue, &tz->thermal_check_work);
+}
+
+static void acpi_thermal_trips_update(struct acpi_thermal *tz, int flag)
+{
+ /*
+ * Use thermal_zone_device_adjust() to carry out the trip points
+ * update, so as to protect thermal_get_trend() from getting stale
+ * trip point temperatures and to prevent thermal_zone_device_update()
+ * invoked from acpi_thermal_check_fn() from producing inconsistent
+ * results.
+ */
+ thermal_zone_device_adjust(tz->thermal_zone, flag);
+ acpi_queue_thermal_check(tz);
}

static int acpi_thermal_get_trip_points(struct acpi_thermal *tz)
{
- int i, ret = acpi_thermal_trips_update(tz, ACPI_TRIPS_INIT);
bool valid;
+ int i;

- if (ret)
- return ret;
+ __acpi_thermal_trips_update(tz, ACPI_TRIPS_INIT);

valid = tz->trips.critical.valid |
tz->trips.hot.valid |
@@ -715,6 +737,7 @@ static struct thermal_zone_device_ops ac
.get_trend = thermal_get_trend,
.hot = acpi_thermal_zone_device_hot,
.critical = acpi_thermal_zone_device_critical,
+ .update = acpi_thermal_adjust_thermal_zone,
};

static int acpi_thermal_zone_sysfs_add(struct acpi_thermal *tz)
@@ -815,12 +838,6 @@ static void acpi_thermal_unregister_ther
Driver Interface
-------------------------------------------------------------------------- */

-static void acpi_queue_thermal_check(struct acpi_thermal *tz)
-{
- if (!work_pending(&tz->thermal_check_work))
- queue_work(acpi_thermal_pm_queue, &tz->thermal_check_work);
-}
-
static void acpi_thermal_notify(acpi_handle handle, u32 event, void *data)
{
struct acpi_device *device = data;
@@ -835,13 +852,11 @@ static void acpi_thermal_notify(acpi_han
break;
case ACPI_THERMAL_NOTIFY_THRESHOLDS:
acpi_thermal_trips_update(tz, ACPI_TRIPS_THRESHOLDS);
- acpi_queue_thermal_check(tz);
acpi_bus_generate_netlink_event(device->pnp.device_class,
dev_name(&device->dev), event, 0);
break;
case ACPI_THERMAL_NOTIFY_DEVICES:
acpi_thermal_trips_update(tz, ACPI_TRIPS_DEVICES);
- acpi_queue_thermal_check(tz);
acpi_bus_generate_netlink_event(device->pnp.device_class,
dev_name(&device->dev), event, 0);
break;