From: Nicolas Pitre <npitre@xxxxxxxxxxxx> 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@xxxxxxxxxxxx> --- drivers/thermal/Kconfig | 12 ++ drivers/thermal/thermal_core.c | 227 +++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) 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) -- 2.47.0