diff mbox series

[4/5] thermal: automatic aggregation support

Message ID 20241112052211.3087348-5-nico@fluxnic.net (mailing list archive)
State New
Headers show
Series thermal: multi-sensor aggregation support | expand

Commit Message

Nicolas Pitre Nov. 12, 2024, 5:19 a.m. UTC
From: Nicolas Pitre <npitre@baylibre.com>

Automatically apply thermal aggregation of multiple related thermal zones
into a single one. Here "related" means such zones must have the same trip
points and cooling devices bound to them. This is an alternative to the
device tree's "thermal-sensors" list for testing purpose without actually
modifying the DTB.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
---
 drivers/thermal/Kconfig        |  12 ++
 drivers/thermal/thermal_core.c | 227 +++++++++++++++++++++++++++++++++
 2 files changed, 239 insertions(+)
diff mbox series

Patch

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 111f07b52a..1b2f319838 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -235,6 +235,18 @@  config THERMAL_AGGREGATION
 	  found in the device tree's "thermal-sensors" list when it contains
 	  more than one entry.
 
+config THERMAL_AGGREGATION_AUTO
+	bool "Automatic Thermal Aggregation support"
+	depends on THERMAL_AGGREGATION
+	help
+	  Automatically apply thermal aggregation of multiple related thermal
+	  zones into a single one. Here "related" means such zones must have
+	  the same trip points and cooling devices bound to them. This is an
+	  alternative to the device tree's "thermal-sensors" list for testing
+	  purpose without actually modifying the DTB. It is highly recommended
+	  that the device tree method be used in preference to this for actual
+	  system deployment.
+
 config THERMAL_EMULATION
 	bool "Thermal emulation mode support"
 	help
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 73a1b30081..934d248aa9 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -755,6 +755,16 @@  static inline void thermal_remove_tz_from_aggregator(struct thermal_zone_device
 {}
 #endif /* CONFIG_THERMAL_AGGREGATION */
 
+#ifdef CONFIG_THERMAL_AGGREGATION_AUTO
+static void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz);
+static void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev);
+#else
+static inline void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz)
+{}
+static inline void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev)
+{}
+#endif /* CONFIG_THERMAL_AGGREGATION_AUTO */
+
 /**
  * thermal_bind_cdev_to_trip - bind a cooling device to a thermal zone
  * @tz:		pointer to struct thermal_zone_device
@@ -1073,6 +1083,8 @@  __thermal_cooling_device_register(struct device_node *np,
 
 	mutex_unlock(&thermal_list_lock);
 
+	thermal_check_cdev_for_aggregation(cdev);
+
 	return cdev;
 
 out_cooling_dev:
@@ -1515,6 +1527,8 @@  thermal_zone_device_register_with_trips(const char *type,
 	if (atomic_cmpxchg(&tz->need_update, 1, 0))
 		thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
 
+	thermal_check_zone_for_aggregation(tz);
+
 	thermal_notify_tz_create(tz);
 
 	thermal_debug_tz_add(tz);
@@ -2068,6 +2082,219 @@  void thermal_zone_device_aggregate(struct thermal_zone_device *tz,
 	mutex_unlock(&thermal_list_lock);
 }
 
+#ifdef CONFIG_THERMAL_AGGREGATION_AUTO
+
+static bool is_aggregator(struct thermal_zone_device *tz)
+{
+	return !strcmp(tz->type, "aggregator");
+}
+
+/**
+ * thermal_trip_related - determine if two trips are equivalent
+ *
+ * @tt1, @tt2: thermal trip specs to compare
+ *
+ * Determine if given trips may be candidates for aggregation.
+ *
+ * Return: true if related for aggregation, false otherwise
+ */
+static bool thermal_trip_related(struct thermal_trip *tt1,
+				 struct thermal_trip *tt2)
+{
+	return tt1->temperature == tt2->temperature &&
+	       tt1->hysteresis == tt2->hysteresis &&
+	       tt1->type == tt2->type &&
+	       tt1->flags == tt2->flags;
+}
+
+static struct thermal_cooling_device *
+trip_to_cdev(struct thermal_zone_device *tz, int trip_idx)
+{
+	struct thermal_instance *ti;
+
+	list_for_each_entry(ti, &tz->thermal_instances, tz_node)
+		if (trip_to_trip_desc(ti->trip) == &tz->trips[trip_idx])
+			return ti->cdev;
+
+	return NULL;
+}
+
+/**
+ * thermal_zone_related - determine if two tz's are candidates for aggregation
+ *
+ * @tz1, @tz2: thermal zones to compare
+ *
+ * Return: true if related for aggregation, false otherwise
+ */
+static bool thermal_zone_related(struct thermal_zone_device *tz1,
+				 struct thermal_zone_device *tz2)
+{
+	/* a tz can't aggregate with itself */
+	if (tz1 == tz2)
+		return false;
+
+	/* no relation possible if ops.should_bind is unset */
+	if (!tz1->ops.should_bind || !tz2->ops.should_bind)
+		return false;
+
+	/* a tz always relates to its aggregator */
+	if (tz1->aggregator == tz2 || tz2->aggregator == tz1)
+		return true;
+
+	/* related tz's must have the same number of trip points */
+	if (tz1->num_trips != tz2->num_trips)
+		return false;
+
+	/* tz's with no cdev bindings are not (yet) considered */
+	if (list_empty(&tz1->thermal_instances) ||
+	    list_empty(&tz2->thermal_instances))
+		return false;
+
+	for (int i = 0; i < tz1->num_trips; i++) {
+		/* all trips must be related */
+		if (!thermal_trip_related(&tz1->trips[i].trip, &tz2->trips[i].trip))
+			return false;
+		/* cdevs for given trips must be the same */
+		if (trip_to_cdev(tz1, i) != trip_to_cdev(tz2, i))
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * find_related_tz - look for a tz aggregation candidate
+ *
+ * @target_tz: tz to compare against
+ *
+ * Return: candidate tz for aggregation, or NULL if none
+ */
+static struct thermal_zone_device *
+find_related_tz(struct thermal_zone_device *target_tz)
+{
+	struct thermal_zone_device *tz;
+
+	list_for_each_entry(tz, &thermal_tz_list, node) {
+		if (is_aggregated(tz))
+			continue;
+		if (is_aggregator(tz))
+			continue;
+		if (!thermal_zone_related(tz, target_tz))
+			continue;
+		return tz;
+	}
+
+	return NULL;
+}
+
+/**
+ * thermal_check_zone_for_aggregation - consider tz for aggregation
+ *
+ * @target_tz: tz to compare against
+ *
+ * Adds the provided tz to a compatible aggregator. If none found, look for
+ * the possibility to create a new aggregator if another compatible tz exists.
+ * This is called, notably, when a new tz is registered and potentially bound
+ * to existing cdevs.
+ */
+static void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz)
+{
+	struct thermal_zone_aggregator *aggr;
+	struct thermal_zone_device *aggr_tz, *tz;
+
+	if (is_aggregator(target_tz))
+		return;
+
+	mutex_lock(&thermal_list_lock);
+	if (!thermal_zone_is_present(target_tz)) {
+		mutex_unlock(&thermal_list_lock);
+		return;
+	}
+
+	/* see if existing aggregators can appropriate this zone  */
+	list_for_each_entry(aggr, &thermal_aggregator_list, node) {
+		aggr_tz = aggr->tz;
+		if (!thermal_zone_related(aggr_tz, target_tz))
+			continue;
+		pr_debug("aggr %s(%d) and zone %s(%d) are related\n",
+			 aggr_tz->type, aggr_tz->id, target_tz->type, target_tz->id);
+		add_tz_to_aggregator(aggr_tz, target_tz);
+		mutex_unlock(&thermal_list_lock);
+		return;
+	}
+
+	/* see if non-aggregated zones can be aggregated */
+	tz = find_related_tz(target_tz);
+	if (!tz) {
+		mutex_unlock(&thermal_list_lock);
+		return;
+	}
+
+	pr_debug("zones %s(%d) and %s(%d) are related\n",
+		 tz->type, tz->id, target_tz->type, target_tz->id);
+
+	mutex_unlock(&thermal_list_lock);
+	aggr_tz = create_thermal_aggregator(target_tz, "aggregator");
+	if (IS_ERR(aggr_tz)) {
+		pr_err("unable to create thermal aggregator (%ld)\n",
+		       PTR_ERR(aggr_tz));
+		return;
+	}
+
+	mutex_lock(&thermal_list_lock);
+
+	/* the lock was momentarily dropped so need to revalide everything */
+	if (thermal_zone_is_present(target_tz)) {
+		tz = find_related_tz(target_tz);
+		if (tz) {
+			add_tz_to_aggregator(aggr_tz, target_tz);
+			add_tz_to_aggregator(aggr_tz, tz);
+			mutex_unlock(&thermal_list_lock);
+			return;
+		}
+	}
+
+	/* our match disappeared in the mean time */
+	free_thermal_aggregator_unlock(aggr_tz);
+}
+
+/**
+ * thermal_check_cdev_for_aggregation - consider aggregation after new cdev registration
+ *
+ * @new_cdev: cdev for which new thermal bindings might create aggregation candidates
+ *
+ * Consider tz's having thermal instance bindings with this new cdev as
+ * candidates for aggregation. This is called when a new cdev is registered
+ * and potentially bound to existing tz's.
+ */
+static void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev)
+{
+	struct thermal_zone_device *tz, *last_tz = NULL;
+	struct thermal_instance *ti;
+
+start_over:
+	mutex_lock(&thermal_list_lock);
+
+	list_for_each_entry(tz, &thermal_tz_list, node) {
+		if (tz == last_tz)
+			continue;
+		if (is_aggregator(tz))
+			continue;
+		list_for_each_entry(ti, &tz->thermal_instances, tz_node) {
+			if (ti->cdev == new_cdev) {
+				last_tz = tz;
+				mutex_unlock(&thermal_list_lock);
+				thermal_check_zone_for_aggregation(tz);
+				/* because the lock was dropped ... */
+				goto start_over;
+			}
+		}
+	}
+
+	mutex_unlock(&thermal_list_lock);
+}
+
+#endif /* CONFIG_THERMAL_AGGREGATION_AUTO */
 #endif /* CONFIG_THERMAL_AGGREGATION */
 
 static void thermal_zone_device_resume(struct work_struct *work)