]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
thermal: core: Add user thresholds support
authorDaniel Lezcano <daniel.lezcano@linaro.org>
Mon, 23 Sep 2024 09:59:57 +0000 (11:59 +0200)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 10 Oct 2024 21:33:37 +0000 (23:33 +0200)
The user thresholds mechanism is a way to have the userspace to tell
the thermal framework to send a notification when a temperature limit
is crossed. There is no id, no hysteresis, just the temperature and
the direction of the limit crossing. That means we can be notified
when a threshold is crossed the way up only, or the way down only or
both ways. That allows to create hysteresis values if it is needed.

A threshold can be added, deleted or flushed. The latter means all
thresholds belonging to a thermal zone will be deleted.

When a threshold is added:

 - if the same threshold (temperature and direction) exists, an error
   is returned

 - if a threshold is specified with the same temperature but a
   different direction, the specified direction is added

 - if there is no threshold with the same temperature then it is
   created

When a threshold is deleted:

 - if the same threshold (temperature and direction) exists, it is
   deleted

 - if a threshold is specified with the same temperature but a
   different direction, the specified direction is removed

 - if there is no threshold with the same temperature, then an error
   is returned

When the threshold are flushed:

 - All thresholds related to a thermal zone are deleted

When a threshold is crossed:

 - the userspace does not need to know which threshold(s) have been
   crossed, it will be notified with the current temperature and the
   previous temperature

 - if multiple thresholds have been crossed between two updates only
   one notification will be send to the userspace, it is pointless to
   send a notification per thresholds crossed as the userspace can
   handle that easily when it has the temperature delta information

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/20240923100005.2532430-2-daniel.lezcano@linaro.org
[ rjw: Subject edit, use BIT(0) and BIT(1) in symbol definitions ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/thermal/Makefile
drivers/thermal/thermal_core.h
drivers/thermal/thermal_thresholds.c [new file with mode: 0644]
drivers/thermal/thermal_thresholds.h [new file with mode: 0644]
include/linux/thermal.h
include/uapi/linux/thermal.h

index 41c4d56beb40d01bde32d48ed87e9ac16c5b363c..1e1559bb971e53922b51b20472f16dc209f92a26 100644 (file)
@@ -6,6 +6,7 @@ CFLAGS_thermal_core.o           := -I$(src)
 obj-$(CONFIG_THERMAL)          += thermal_sys.o
 thermal_sys-y                  += thermal_core.o thermal_sysfs.o
 thermal_sys-y                  += thermal_trip.o thermal_helpers.o
+thermal_sys-y                  += thermal_thresholds.o
 
 # netlink interface to manage the thermal framework
 thermal_sys-$(CONFIG_THERMAL_NETLINK)          += thermal_netlink.o
index a64d39b1c86b235cb1dd624b17e1599a70fe74ad..1ea91d59498b2e475b36d10a3f05537b1c2ddabc 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/thermal.h>
 
 #include "thermal_netlink.h"
+#include "thermal_thresholds.h"
 #include "thermal_debugfs.h"
 
 struct thermal_attr {
@@ -139,6 +140,7 @@ struct thermal_zone_device {
 #ifdef CONFIG_THERMAL_DEBUGFS
        struct thermal_debugfs *debugfs;
 #endif
+       struct list_head user_thresholds;
        struct thermal_trip_desc trips[] __counted_by(num_trips);
 };
 
diff --git a/drivers/thermal/thermal_thresholds.c b/drivers/thermal/thermal_thresholds.c
new file mode 100644 (file)
index 0000000..f33b6d5
--- /dev/null
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Thermal thresholds
+ */
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <linux/slab.h>
+
+#include "thermal_core.h"
+#include "thermal_thresholds.h"
+
+int thermal_thresholds_init(struct thermal_zone_device *tz)
+{
+       INIT_LIST_HEAD(&tz->user_thresholds);
+
+       return 0;
+}
+
+void thermal_thresholds_flush(struct thermal_zone_device *tz)
+{
+       struct list_head *thresholds = &tz->user_thresholds;
+       struct user_threshold *entry, *tmp;
+
+       lockdep_assert_held(&tz->lock);
+
+       list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
+               list_del(&entry->list_node);
+               kfree(entry);
+       }
+
+       __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
+}
+
+void thermal_thresholds_exit(struct thermal_zone_device *tz)
+{
+       thermal_thresholds_flush(tz);
+}
+
+static int __thermal_thresholds_cmp(void *data,
+                                   const struct list_head *l1,
+                                   const struct list_head *l2)
+{
+       struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
+       struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
+
+       return t1->temperature - t2->temperature;
+}
+
+static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
+                                                       int temperature)
+{
+       struct user_threshold *t;
+
+       list_for_each_entry(t, thresholds, list_node)
+               if (t->temperature == temperature)
+                       return t;
+
+       return NULL;
+}
+
+static bool __thermal_threshold_is_crossed(struct user_threshold *threshold, int temperature,
+                                          int last_temperature, int direction,
+                                          int *low, int *high)
+{
+
+       if (temperature >= threshold->temperature) {
+               if (threshold->temperature > *low &&
+                   THERMAL_THRESHOLD_WAY_DOWN & threshold->direction)
+                       *low = threshold->temperature;
+
+               if (last_temperature < threshold->temperature &&
+                   threshold->direction & direction)
+                       return true;
+       } else {
+               if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP
+                   & threshold->direction)
+                       *high = threshold->temperature;
+
+               if (last_temperature >= threshold->temperature &&
+                   threshold->direction & direction)
+                       return true;
+       }
+
+       return false;
+}
+
+static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
+                                             int last_temperature, int *low, int *high)
+{
+       struct user_threshold *t;
+
+       list_for_each_entry(t, thresholds, list_node) {
+               if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
+                                                  THERMAL_THRESHOLD_WAY_UP, low, high))
+                       return true;
+       }
+
+       return false;
+}
+
+static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
+                                              int last_temperature, int *low, int *high)
+{
+       struct user_threshold *t;
+
+       list_for_each_entry_reverse(t, thresholds, list_node) {
+               if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
+                                                  THERMAL_THRESHOLD_WAY_DOWN, low, high))
+                       return true;
+       }
+
+       return false;
+}
+
+void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
+{
+       struct list_head *thresholds = &tz->user_thresholds;
+
+       int temperature = tz->temperature;
+       int last_temperature = tz->last_temperature;
+       bool notify;
+
+       lockdep_assert_held(&tz->lock);
+
+       /*
+        * We need a second update in order to detect a threshold being crossed
+        */
+       if (last_temperature == THERMAL_TEMP_INVALID)
+               return;
+
+       /*
+        * The temperature is stable, so obviously we can not have
+        * crossed a threshold.
+        */
+       if (last_temperature == temperature)
+               return;
+
+       /*
+        * Since last update the temperature:
+        * - increased : thresholds are crossed the way up
+        * - decreased : thresholds are crossed the way down
+        */
+       if (temperature > last_temperature)
+               notify = thermal_thresholds_handle_raising(thresholds, temperature,
+                                                          last_temperature, low, high);
+       else
+               notify = thermal_thresholds_handle_dropping(thresholds, temperature,
+                                                           last_temperature, low, high);
+
+       if (notify)
+               pr_debug("A threshold has been crossed the way %s, with a temperature=%d, last_temperature=%d\n",
+                        temperature > last_temperature ? "up" : "down", temperature, last_temperature);
+}
+
+int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction)
+{
+       struct list_head *thresholds = &tz->user_thresholds;
+       struct user_threshold *t;
+
+       lockdep_assert_held(&tz->lock);
+
+       t = __thermal_thresholds_find(thresholds, temperature);
+       if (t) {
+               if (t->direction == direction)
+                       return -EEXIST;
+
+               t->direction |= direction;
+       } else {
+
+               t = kmalloc(sizeof(*t), GFP_KERNEL);
+               if (!t)
+                       return -ENOMEM;
+
+               INIT_LIST_HEAD(&t->list_node);
+               t->temperature = temperature;
+               t->direction = direction;
+               list_add(&t->list_node, thresholds);
+               list_sort(NULL, thresholds, __thermal_thresholds_cmp);
+       }
+
+       __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
+
+       return 0;
+}
+
+int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction)
+{
+       struct list_head *thresholds = &tz->user_thresholds;
+       struct user_threshold *t;
+
+       lockdep_assert_held(&tz->lock);
+
+       t = __thermal_thresholds_find(thresholds, temperature);
+       if (!t)
+               return -ENOENT;
+
+       if (t->direction == direction) {
+               list_del(&t->list_node);
+               kfree(t);
+       } else {
+               t->direction &= ~direction;
+       }
+
+       __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
+
+       return 0;
+}
+
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+                               int (*cb)(struct user_threshold *, void *arg), void *arg)
+{
+       struct list_head *thresholds = &tz->user_thresholds;
+       struct user_threshold *entry;
+       int ret;
+
+       lockdep_assert_held(&tz->lock);
+
+       list_for_each_entry(entry, thresholds, list_node) {
+               ret = cb(entry, arg);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
diff --git a/drivers/thermal/thermal_thresholds.h b/drivers/thermal/thermal_thresholds.h
new file mode 100644 (file)
index 0000000..232f4e8
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __THERMAL_THRESHOLDS_H__
+#define __THERMAL_THRESHOLDS_H__
+
+struct user_threshold {
+       struct list_head list_node;
+       int temperature;
+       int direction;
+};
+
+int thermal_thresholds_init(struct thermal_zone_device *tz);
+void thermal_thresholds_exit(struct thermal_zone_device *tz);
+void thermal_thresholds_flush(struct thermal_zone_device *tz);
+void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high);
+int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+                               int (*cb)(struct user_threshold *, void *arg), void *arg);
+#endif
index 25ea8fe2313e6d0a53192974a80d92f50f0ff03e..bcaa92732e14a4e827c5b2fd6f334bcc0c56a584 100644 (file)
@@ -56,6 +56,9 @@ enum thermal_notify_event {
        THERMAL_TZ_UNBIND_CDEV, /* Cooling dev is unbind from the thermal zone */
        THERMAL_INSTANCE_WEIGHT_CHANGED, /* Thermal instance weight changed */
        THERMAL_TZ_RESUME, /* Thermal zone is resuming after system sleep */
+       THERMAL_TZ_ADD_THRESHOLD, /* Threshold added */
+       THERMAL_TZ_DEL_THRESHOLD, /* Threshold deleted */
+       THERMAL_TZ_FLUSH_THRESHOLDS, /* All thresholds deleted */
 };
 
 /**
index fc78bf3aead79d34ff7497a064d5958777b90464..2e6f60a36173506ea14dc12afbe1a5e4ab27b657 100644 (file)
@@ -3,6 +3,8 @@
 #define _UAPI_LINUX_THERMAL_H
 
 #define THERMAL_NAME_LENGTH    20
+#define THERMAL_THRESHOLD_WAY_UP       BIT(0)
+#define THERMAL_THRESHOLD_WAY_DOWN     BIT(1)
 
 enum thermal_device_mode {
        THERMAL_DEVICE_DISABLED = 0,