All of lore.kernel.org
 help / color / mirror / Atom feed
From: Javi Merino <javi.merino@arm.com>
To: linux-pm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, rui.zang@intel.com,
	edubezval@gmail.com, Javi Merino <javi.merino@arm.com>,
	Zhang Rui <rui.zhang@intel.com>
Subject: [PATCH v3 1/4] thermal: Add support for hierarchical thermal zones
Date: Wed, 25 Nov 2015 15:09:43 +0000	[thread overview]
Message-ID: <1448464186-26289-2-git-send-email-javi.merino@arm.com> (raw)
In-Reply-To: <1448464186-26289-1-git-send-email-javi.merino@arm.com>

Add the ability to stack thermal zones on top of each other, creating a
hierarchy of thermal zones.

Cc: Zhang Rui <rui.zhang@intel.com>
Cc: Eduardo Valentin <edubezval@gmail.com>
Signed-off-by: Javi Merino <javi.merino@arm.com>
---
 Documentation/thermal/sysfs-api.txt |  39 +++++++
 drivers/thermal/thermal_core.c      | 210 +++++++++++++++++++++++++++++++++++-
 include/linux/thermal.h             |  41 ++++++-
 3 files changed, 284 insertions(+), 6 deletions(-)

diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt
index 10f062ea6bc2..dda24a0bdeec 100644
--- a/Documentation/thermal/sysfs-api.txt
+++ b/Documentation/thermal/sysfs-api.txt
@@ -72,6 +72,45 @@ temperature) and throttle appropriate devices.
     It deletes the corresponding entry form /sys/class/thermal folder and
     unbind all the thermal cooling devices it uses.
 
+
+1.1.3 int thermal_zone_add_subtz(struct thermal_zone_device *tz,
+                                 struct thermal_zone_device *subtz)
+
+    Add subtz to the list of slaves of tz.  This lets you create a
+    hierarchy of thermal zones.  The hierarchy must be a Direct
+    Acyclic Graph (DAG).  If a loop is detected,
+    thermal_zone_get_temp() will return -EDEADLK to prevent the
+    deadlock.  thermal_zone_add_subtz() does not affect subtz.
+
+    When calculating the temperature of thermal zone tz, report it as
+    an aggregation of the temperatures of the sub thermal zones.  For
+    details of the aggregation function see the enum
+    thermal_aggregation_function.
+
+    For example, if you have an SoC with a thermal sensor in each cpu
+    of the two cpus you could have a thermal zone to monitor each
+    sensor, let's call them cpu0_tz and cpu1_tz.  You could then have a
+    a SoC thermal zone (soc_tz) without a get_temp() op which can be
+    configured like this:
+
+    thermal_zone_add_subtz(soc_tz, cpu0_tz);
+    thermal_zone_add_subtz(soc_tz, cpu1_tz);
+
+    Now soc_tz's temperature is the aggregation of cpu0_tz and
+    cpu1_tz.
+
+
+1.1.4 int thermal_zone_del_subtz(struct thermal_zone_device *tz,
+                                 struct thermal_zone_device *subtz)
+
+    Delete subtz from the slave thermal zones of tz.  This basically
+    reverses what thermal_zone_add_subtz() does.  If tz still has
+    other slave thermal zones, its temperature would be the
+    aggregation of the remaining slave thermal zones.  If tz no longer
+    has slave thermal zones, thermal_zone_get_temp() will return
+    -EINVAL.
+
+
 1.2 thermal cooling device interface
 1.2.1 struct thermal_cooling_device *thermal_cooling_device_register(char *name,
 		void *devdata, struct thermal_cooling_device_ops *)
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index d9e525cc9c1c..98bdb63b3b95 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -61,6 +61,20 @@ static DEFINE_MUTEX(thermal_governor_lock);
 
 static struct thermal_governor *def_governor;
 
+/**
+ * struct thermal_zone_link - a link between "master" and "slave" thermal zones
+ * @weight:	weight of the @slave thermal zone in the master
+ *		calculation.  Used when the master thermal zone's
+ *		aggregation function is THERMAL_AGG_WEIGHTED_AVG
+ * @slave:	pointer to the slave thermal zone
+ * @node:	list node in the master thermal zone's slave_tzs list.
+ */
+struct thermal_zone_link {
+	int weight;
+	struct thermal_zone_device *slave;
+	struct list_head node;
+};
+
 static struct thermal_governor *__find_governor(const char *name)
 {
 	struct thermal_governor *pos;
@@ -465,6 +479,80 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
 }
 
 /**
+ * get_subtz_temp() - get the aggregate temperature of all the sub thermal zones
+ * @tz:	thermal zone pointer
+ * @temp: pointer in which to store the result
+ *
+ * Go through all the thermal zones listed in @tz slave_tzs and
+ * calculate the aggregate temperature of all of them.  The result is
+ * stored in @temp.  The function used for aggregation is defined by
+ * the thermal zone's "slave_agg_function" parameter.  It can only be
+ * THERMAL_AGG_MAX or THERMAL_AGG_WEIGHTED_AVG.  If THERMAL_AGG_MAX is
+ * used then the @temp holds the maximum temperature of all slave
+ * thermal zones.  With THERMAL_AGG_WEIGHTED_AVG a weighted average of
+ * all temperatures is calculated.  Each thermal zone's temperature is
+ * multiplied by its weight and the result is divided by the sum of
+ * all weights.  If all weights are zero, the result is the average
+ * weight of the thermal zones.
+ *
+ * Return: 0 on success, -EINVAL if there are no slave thermal zones,
+ * -E* if thermal_zone_get_temp() fails for any of the slave thermal
+ * zones.
+ */
+static int get_subtz_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct thermal_zone_link *link;
+	int ret_temp, total_weight = 0;
+	bool all_weights_are_zero = false;
+
+	if (list_empty(&tz->slave_tzs))
+		return -EINVAL;
+
+	if (tz->slave_agg_function == THERMAL_AGG_WEIGHTED_AVG) {
+		int total_instances = 0;
+
+		list_for_each_entry(link, &tz->slave_tzs, node) {
+			total_weight += link->weight;
+			total_instances++;
+		}
+
+		if (!total_weight) {
+			all_weights_are_zero = true;
+			total_weight = total_instances;
+		} else {
+			all_weights_are_zero = false;
+		}
+
+		ret_temp = 0;
+	} else if (tz->slave_agg_function == THERMAL_AGG_MAX) {
+		ret_temp = INT_MIN;
+	}
+
+	list_for_each_entry(link, &tz->slave_tzs, node) {
+		int this_temp, ret;
+
+		ret = thermal_zone_get_temp(link->slave, &this_temp);
+		if (ret)
+			return ret;
+
+		if (tz->slave_agg_function == THERMAL_AGG_MAX) {
+			if (this_temp > ret_temp)
+				ret_temp = this_temp;
+		} else if (tz->slave_agg_function == THERMAL_AGG_WEIGHTED_AVG) {
+			int weight = all_weights_are_zero ? 1 : link->weight;
+
+			ret_temp += weight * this_temp;
+		}
+	}
+
+	if (tz->slave_agg_function == THERMAL_AGG_WEIGHTED_AVG)
+		ret_temp /= total_weight;
+
+	*temp = ret_temp;
+	return 0;
+}
+
+/**
  * thermal_zone_get_temp() - returns the temperature of a thermal zone
  * @tz: a valid pointer to a struct thermal_zone_device
  * @temp: a valid pointer to where to store the resulting temperature.
@@ -481,11 +569,22 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
 	int crit_temp = INT_MAX;
 	enum thermal_trip_type type;
 
-	if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
+	if (!tz || IS_ERR(tz))
 		goto exit;
 
+	/* Avoid loops */
+	if (tz->slave_tz_visited)
+		return -EDEADLK;
+
 	mutex_lock(&tz->lock);
 
+	tz->slave_tz_visited = true;
+
+	if (!tz->ops->get_temp) {
+		ret = get_subtz_temp(tz, temp);
+		goto unlock;
+	}
+
 	ret = tz->ops->get_temp(tz, temp);
 
 	if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
@@ -506,7 +605,9 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
 		if (!ret && *temp < crit_temp)
 			*temp = tz->emul_temperature;
 	}
- 
+
+unlock:
+	tz->slave_tz_visited = false;
 	mutex_unlock(&tz->lock);
 exit:
 	return ret;
@@ -540,9 +641,6 @@ void thermal_zone_device_update(struct thermal_zone_device *tz)
 {
 	int count;
 
-	if (!tz->ops->get_temp)
-		return;
-
 	update_temperature(tz);
 
 	for (count = 0; count < tz->trips; count++)
@@ -1785,10 +1883,17 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
 	if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
 		return ERR_PTR(-EINVAL);
 
+	if (tzp && (tzp->agg_func != THERMAL_AGG_MAX) &&
+	    (tzp->agg_func != THERMAL_AGG_WEIGHTED_AVG))
+		return ERR_PTR(-EINVAL);
+
 	tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL);
 	if (!tz)
 		return ERR_PTR(-ENOMEM);
 
+	if (tzp)
+		tz->slave_agg_function = tzp->agg_func;
+	INIT_LIST_HEAD(&tz->slave_tzs);
 	INIT_LIST_HEAD(&tz->thermal_instances);
 	idr_init(&tz->idr);
 	mutex_init(&tz->lock);
@@ -1921,6 +2026,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
 	const struct thermal_zone_params *tzp;
 	struct thermal_cooling_device *cdev;
 	struct thermal_zone_device *pos = NULL;
+	struct thermal_zone_link *link, *tmp;
 
 	if (!tz)
 		return;
@@ -1958,6 +2064,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
 
 	mutex_unlock(&thermal_list_lock);
 
+	list_for_each_entry_safe(link, tmp, &tz->slave_tzs, node)
+		thermal_zone_del_subtz(tz, link->slave);
+
 	thermal_zone_device_set_polling(tz, 0);
 
 	if (tz->type[0])
@@ -1980,6 +2089,97 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
 EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
 
 /**
+ * thermal_zone_add_subtz() - add a sub thermal zone to a thermal zone
+ * @tz:	pointer to the master thermal zone
+ * @subtz: pointer to the slave thermal zone
+ * @weight: relative contribution of the sub thermal zone
+ *
+ * Add @subtz to the list of slave thermal zones of @tz.  If @tz
+ * doesn't provide a get_temp() op, its temperature will be calculated
+ * as a combination of the temperatures of its sub thermal zones.  The
+ * @weight is the relative contribution of this thermal zone when
+ * using THERMAL_AGG_WEIGHTED_AVG.  Set @weight to
+ * THERMAL_WEIGHT_DEFAULT for all sub thermal zones to do a simple
+ * average.  See get_sub_tz_temp() for more information on how the
+ * temperature for @tz is calculated.
+ *
+ * Return: 0 on success, -EINVAL if @tz or @subtz are not valid
+ * pointers, -EEXIST if the link already exists or -ENOMEM if we ran
+ * out of memory.
+ */
+int thermal_zone_add_subtz(struct thermal_zone_device *tz,
+			   struct thermal_zone_device *subtz, int weight)
+{
+	int ret;
+	struct thermal_zone_link *link;
+
+	if (IS_ERR_OR_NULL(tz) || IS_ERR_OR_NULL(subtz))
+		return -EINVAL;
+
+	mutex_lock(&tz->lock);
+
+	list_for_each_entry(link, &tz->slave_tzs, node) {
+		if (link->slave == subtz) {
+			ret = -EEXIST;
+			goto unlock;
+		}
+	}
+
+	link = kzalloc(sizeof(*link), GFP_KERNEL);
+	if (!link) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	link->slave = subtz;
+	link->weight = weight;
+	list_add_tail(&link->node, &tz->slave_tzs);
+
+	ret = 0;
+
+unlock:
+	mutex_unlock(&tz->lock);
+
+	return ret;
+}
+
+/**
+ * thermal_zone_del_subtz() - delete a sub thermal zone from its master thermal zone
+ * @tz:	pointer to the master thermal zone
+ * @subtz: pointer to the slave thermal zone
+ *
+ * Remove @subtz from the list of slave thermal zones of @tz.
+ *
+ * Return: 0 on success, -EINVAL if either thermal is invalid or if
+ * @subtz is not a slave of @tz.
+ */
+int thermal_zone_del_subtz(struct thermal_zone_device *tz,
+			   struct thermal_zone_device *subtz)
+{
+	int ret = -EINVAL;
+	struct thermal_zone_link *link;
+
+	if (IS_ERR_OR_NULL(tz) || IS_ERR_OR_NULL(subtz))
+		return -EINVAL;
+
+	mutex_lock(&tz->lock);
+
+	list_for_each_entry(link, &tz->slave_tzs, node) {
+		if (link->slave == subtz) {
+			list_del(&link->node);
+			kfree(link);
+
+			ret = 0;
+			break;
+		}
+	}
+
+	mutex_unlock(&tz->lock);
+
+	return ret;
+}
+
+/**
  * thermal_zone_get_zone_by_name() - search for a zone and returns its ref
  * @name: thermal zone name to fetch the temperature
  *
diff --git a/include/linux/thermal.h b/include/linux/thermal.h
index 4014a59828fc..dc3d2ab77ab6 100644
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -40,7 +40,7 @@
 /* No upper/lower limit requirement */
 #define THERMAL_NO_LIMIT	((u32)~0)
 
-/* Default weight of a bound cooling device */
+/* Default weight for cooling devices and sub thermal zones */
 #define THERMAL_WEIGHT_DEFAULT 0
 
 /* Unit conversion macros */
@@ -89,6 +89,18 @@ enum thermal_trend {
 	THERMAL_TREND_DROP_FULL, /* apply lowest cooling action */
 };
 
+/**
+ * enum thermal_aggregation_function - aggregation function for sub thermal zones
+ * @THERMAL_AGG_MAX:	calculate the maximum of all sub thermal zones
+
+ * @THERMAL_AGG_WEIGHTED_AVG:	calculate the weighted average of all
+ *				sub thermal zones
+ */
+enum thermal_aggregation_function {
+	THERMAL_AGG_MAX,
+	THERMAL_AGG_WEIGHTED_AVG,
+};
+
 struct thermal_zone_device_ops {
 	int (*bind) (struct thermal_zone_device *,
 		     struct thermal_cooling_device *);
@@ -170,6 +182,12 @@ struct thermal_attr {
  * @ops:	operations this &thermal_zone_device supports
  * @tzp:	thermal zone parameters
  * @governor:	pointer to the governor for this thermal zone
+ * @slave_agg_function: aggretion function to calculate the
+ *			temperature as a combination of the slave thermal zones
+ *			of this thermal zone.  See enum
+ *			thermal_aggregation_function for valid values
+ * @slave_tzs:	list of thermal zones that are a slave of this thermal zone
+ * @slave_tz_visited: used to detect loops in stacked thermal zones
  * @governor_data:	private pointer for governor data
  * @thermal_instances:	list of &struct thermal_instance of this thermal zone
  * @idr:	&struct idr to generate unique id for this zone's cooling
@@ -197,6 +215,9 @@ struct thermal_zone_device {
 	struct thermal_zone_device_ops *ops;
 	struct thermal_zone_params *tzp;
 	struct thermal_governor *governor;
+	enum thermal_aggregation_function slave_agg_function;
+	struct list_head slave_tzs;
+	bool slave_tz_visited;
 	void *governor_data;
 	struct list_head thermal_instances;
 	struct idr idr;
@@ -311,6 +332,13 @@ struct thermal_zone_params {
 	 * 		Used by thermal zone drivers (default 0).
 	 */
 	int offset;
+
+	/*
+	 * aggregation function to use when calculating the
+	 * temperature as a combination of multiple sub thermal
+	 * zones
+	 */
+	enum thermal_aggregation_function agg_func;
 };
 
 struct thermal_genl_event {
@@ -405,6 +433,10 @@ struct thermal_cooling_device *
 thermal_of_cooling_device_register(struct device_node *np, char *, void *,
 				   const struct thermal_cooling_device_ops *);
 void thermal_cooling_device_unregister(struct thermal_cooling_device *);
+int thermal_zone_add_subtz(struct thermal_zone_device *,
+			   struct thermal_zone_device *, int);
+int thermal_zone_del_subtz(struct thermal_zone_device *,
+			   struct thermal_zone_device *);
 struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name);
 int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
 
@@ -457,6 +489,13 @@ thermal_of_cooling_device_register(struct device_node *np,
 static inline void thermal_cooling_device_unregister(
 	struct thermal_cooling_device *cdev)
 { }
+static inline int thermal_zone_add_subtz(struct thermal_zone_device *tz,
+					 struct thermal_zone_device *subtz,
+					 int weight)
+{ return -ENODEV; }
+static inline int thermal_zone_del_subtz(struct thermal_zone_device *tz,
+					 struct thermal_zone_device *subtz)
+{ return -ENODEV; }
 static inline struct thermal_zone_device *thermal_zone_get_zone_by_name(
 		const char *name)
 { return ERR_PTR(-ENODEV); }
-- 
1.9.1


  reply	other threads:[~2015-11-25 15:10 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-11-25 15:09 [PATCH v3 0/4] Hierarchical thermal zones Javi Merino
2015-11-25 15:09 ` Javi Merino [this message]
2016-03-03  3:12   ` [PATCH v3 1/4] thermal: Add support for hierarchical " Eduardo Valentin
2016-03-03  3:23     ` Eduardo Valentin
2015-11-25 15:09 ` [PATCH v3 2/4] devicetree: bindings: let thermal-sensor point to other " Javi Merino
2015-11-25 17:54   ` Mark Rutland
2015-11-25 18:41     ` Javi Merino
2015-11-25 18:41       ` Javi Merino
2016-01-04 14:17   ` Sascha Hauer
2016-03-03  3:21     ` Eduardo Valentin
2016-03-21 11:55       ` Javi Merino
2016-03-22 15:13         ` Eduardo Valentin
2016-03-22 15:13           ` Eduardo Valentin
2016-03-03  3:19   ` Eduardo Valentin
2015-11-25 15:09 ` [PATCH v3 3/4] thermal: of: parse stacked thermal zones from device tree Javi Merino
2015-11-25 15:09 ` [PATCH v3 4/4] thermal: show the sub thermal zones in sysfs Javi Merino
2016-03-03  3:17   ` Eduardo Valentin
2016-03-16 22:06   ` Eduardo Valentin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1448464186-26289-2-git-send-email-javi.merino@arm.com \
    --to=javi.merino@arm.com \
    --cc=edubezval@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=rui.zang@intel.com \
    --cc=rui.zhang@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.