2008-01-17 10:51:08 +03:00
|
|
|
/*
|
|
|
|
* thermal.c - Generic Thermal Management Sysfs support.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Intel Corp
|
|
|
|
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
|
|
|
|
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
|
|
|
|
*
|
|
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; version 2 of the License.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
|
|
*
|
|
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
*/
|
|
|
|
|
2012-03-21 23:55:02 +04:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/err.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
|
|
|
#include <linux/slab.h>
|
2008-01-17 10:51:08 +03:00
|
|
|
#include <linux/kdev_t.h>
|
|
|
|
#include <linux/idr.h>
|
|
|
|
#include <linux/thermal.h>
|
2008-12-03 20:55:32 +03:00
|
|
|
#include <linux/reboot.h>
|
2013-05-17 15:52:02 +04:00
|
|
|
#include <linux/string.h>
|
2013-09-26 23:55:01 +04:00
|
|
|
#include <linux/of.h>
|
2010-10-27 02:03:29 +04:00
|
|
|
#include <net/netlink.h>
|
|
|
|
#include <net/genetlink.h>
|
2015-10-30 11:31:58 +03:00
|
|
|
#include <linux/suspend.h>
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2014-07-29 14:50:48 +04:00
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include <trace/events/thermal.h>
|
|
|
|
|
2012-09-18 09:34:53 +04:00
|
|
|
#include "thermal_core.h"
|
2013-07-03 23:14:28 +04:00
|
|
|
#include "thermal_hwmon.h"
|
2012-09-18 09:34:53 +04:00
|
|
|
|
2008-04-21 12:07:13 +04:00
|
|
|
MODULE_AUTHOR("Zhang Rui");
|
2008-01-17 10:51:08 +03:00
|
|
|
MODULE_DESCRIPTION("Generic thermal management sysfs support");
|
2013-04-24 01:48:13 +04:00
|
|
|
MODULE_LICENSE("GPL v2");
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
static DEFINE_IDR(thermal_tz_idr);
|
|
|
|
static DEFINE_IDR(thermal_cdev_idr);
|
|
|
|
static DEFINE_MUTEX(thermal_idr_lock);
|
|
|
|
|
|
|
|
static LIST_HEAD(thermal_tz_list);
|
|
|
|
static LIST_HEAD(thermal_cdev_list);
|
2012-09-18 09:34:57 +04:00
|
|
|
static LIST_HEAD(thermal_governor_list);
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
static DEFINE_MUTEX(thermal_list_lock);
|
2012-09-18 09:34:57 +04:00
|
|
|
static DEFINE_MUTEX(thermal_governor_lock);
|
|
|
|
|
2015-10-30 11:31:58 +03:00
|
|
|
static atomic_t in_suspend;
|
|
|
|
|
2014-01-24 06:23:19 +04:00
|
|
|
static struct thermal_governor *def_governor;
|
|
|
|
|
2012-09-18 09:34:57 +04:00
|
|
|
static struct thermal_governor *__find_governor(const char *name)
|
|
|
|
{
|
|
|
|
struct thermal_governor *pos;
|
|
|
|
|
2014-01-24 06:23:19 +04:00
|
|
|
if (!name || !name[0])
|
|
|
|
return def_governor;
|
|
|
|
|
2012-09-18 09:34:57 +04:00
|
|
|
list_for_each_entry(pos, &thermal_governor_list, governor_list)
|
2014-10-14 02:55:01 +04:00
|
|
|
if (!strncasecmp(name, pos->name, THERMAL_NAME_LENGTH))
|
2012-09-18 09:34:57 +04:00
|
|
|
return pos;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-26 22:00:27 +03:00
|
|
|
/**
|
|
|
|
* bind_previous_governor() - bind the previous governor of the thermal zone
|
|
|
|
* @tz: a valid pointer to a struct thermal_zone_device
|
|
|
|
* @failed_gov_name: the name of the governor that failed to register
|
|
|
|
*
|
|
|
|
* Register the previous governor of the thermal zone after a new
|
|
|
|
* governor has failed to be bound.
|
|
|
|
*/
|
|
|
|
static void bind_previous_governor(struct thermal_zone_device *tz,
|
|
|
|
const char *failed_gov_name)
|
|
|
|
{
|
|
|
|
if (tz->governor && tz->governor->bind_to_tz) {
|
|
|
|
if (tz->governor->bind_to_tz(tz)) {
|
|
|
|
dev_err(&tz->device,
|
|
|
|
"governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",
|
|
|
|
failed_gov_name, tz->governor->name, tz->type);
|
|
|
|
tz->governor = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* thermal_set_governor() - Switch to another governor
|
|
|
|
* @tz: a valid pointer to a struct thermal_zone_device
|
|
|
|
* @new_gov: pointer to the new governor
|
|
|
|
*
|
|
|
|
* Change the governor of thermal zone @tz.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, an error if the new governor's bind_to_tz() failed.
|
|
|
|
*/
|
|
|
|
static int thermal_set_governor(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_governor *new_gov)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (tz->governor && tz->governor->unbind_from_tz)
|
|
|
|
tz->governor->unbind_from_tz(tz);
|
|
|
|
|
|
|
|
if (new_gov && new_gov->bind_to_tz) {
|
|
|
|
ret = new_gov->bind_to_tz(tz);
|
|
|
|
if (ret) {
|
|
|
|
bind_previous_governor(tz, new_gov->name);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tz->governor = new_gov;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-09-18 09:34:57 +04:00
|
|
|
int thermal_register_governor(struct thermal_governor *governor)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
const char *name;
|
|
|
|
struct thermal_zone_device *pos;
|
|
|
|
|
|
|
|
if (!governor)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
err = -EBUSY;
|
|
|
|
if (__find_governor(governor->name) == NULL) {
|
|
|
|
err = 0;
|
|
|
|
list_add(&governor->governor_list, &thermal_governor_list);
|
2014-01-24 06:23:19 +04:00
|
|
|
if (!def_governor && !strncmp(governor->name,
|
|
|
|
DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH))
|
|
|
|
def_governor = governor;
|
2012-09-18 09:34:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
2014-01-24 06:23:19 +04:00
|
|
|
/*
|
|
|
|
* only thermal zones with specified tz->tzp->governor_name
|
|
|
|
* may run with tz->govenor unset
|
|
|
|
*/
|
2012-09-18 09:34:57 +04:00
|
|
|
if (pos->governor)
|
|
|
|
continue;
|
2014-01-24 06:23:19 +04:00
|
|
|
|
|
|
|
name = pos->tzp->governor_name;
|
|
|
|
|
2015-02-26 22:00:27 +03:00
|
|
|
if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = thermal_set_governor(pos, governor);
|
|
|
|
if (ret)
|
|
|
|
dev_err(&pos->device,
|
|
|
|
"Failed to set governor %s for thermal zone %s: %d\n",
|
|
|
|
governor->name, pos->type, ret);
|
|
|
|
}
|
2012-09-18 09:34:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
void thermal_unregister_governor(struct thermal_governor *governor)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *pos;
|
|
|
|
|
|
|
|
if (!governor)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
if (__find_governor(governor->name) == NULL)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
2014-10-14 02:55:01 +04:00
|
|
|
if (!strncasecmp(pos->governor->name, governor->name,
|
2012-09-18 09:34:57 +04:00
|
|
|
THERMAL_NAME_LENGTH))
|
2015-02-26 22:00:27 +03:00
|
|
|
thermal_set_governor(pos, NULL);
|
2012-09-18 09:34:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
list_del(&governor->governor_list);
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
return;
|
|
|
|
}
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
static int get_idr(struct idr *idr, struct mutex *lock, int *id)
|
|
|
|
{
|
2013-02-28 05:04:46 +04:00
|
|
|
int ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (lock)
|
|
|
|
mutex_lock(lock);
|
2013-02-28 05:04:46 +04:00
|
|
|
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
|
2008-01-17 10:51:08 +03:00
|
|
|
if (lock)
|
|
|
|
mutex_unlock(lock);
|
2013-02-28 05:04:46 +04:00
|
|
|
if (unlikely(ret < 0))
|
|
|
|
return ret;
|
|
|
|
*id = ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void release_idr(struct idr *idr, struct mutex *lock, int id)
|
|
|
|
{
|
|
|
|
if (lock)
|
|
|
|
mutex_lock(lock);
|
|
|
|
idr_remove(idr, id);
|
|
|
|
if (lock)
|
|
|
|
mutex_unlock(lock);
|
|
|
|
}
|
|
|
|
|
2012-09-18 09:34:54 +04:00
|
|
|
int get_tz_trend(struct thermal_zone_device *tz, int trip)
|
|
|
|
{
|
|
|
|
enum thermal_trend trend;
|
|
|
|
|
2013-05-30 01:37:00 +04:00
|
|
|
if (tz->emul_temperature || !tz->ops->get_trend ||
|
|
|
|
tz->ops->get_trend(tz, trip, &trend)) {
|
2012-09-18 09:34:54 +04:00
|
|
|
if (tz->temperature > tz->last_temperature)
|
|
|
|
trend = THERMAL_TREND_RAISING;
|
|
|
|
else if (tz->temperature < tz->last_temperature)
|
|
|
|
trend = THERMAL_TREND_DROPPING;
|
|
|
|
else
|
|
|
|
trend = THERMAL_TREND_STABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return trend;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(get_tz_trend);
|
|
|
|
|
|
|
|
struct thermal_instance *get_thermal_instance(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_cooling_device *cdev, int trip)
|
|
|
|
{
|
|
|
|
struct thermal_instance *pos = NULL;
|
|
|
|
struct thermal_instance *target_instance = NULL;
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
mutex_lock(&cdev->lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
|
|
|
|
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
|
|
|
|
target_instance = pos;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&cdev->lock);
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
return target_instance;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(get_thermal_instance);
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
static void print_bind_err_msg(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_cooling_device *cdev, int ret)
|
|
|
|
{
|
|
|
|
dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
|
|
|
|
tz->type, cdev->type, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __bind(struct thermal_zone_device *tz, int mask,
|
2013-07-16 23:26:28 +04:00
|
|
|
struct thermal_cooling_device *cdev,
|
2015-02-18 19:04:21 +03:00
|
|
|
unsigned long *limits,
|
|
|
|
unsigned int weight)
|
2012-09-18 09:34:59 +04:00
|
|
|
{
|
|
|
|
int i, ret;
|
|
|
|
|
|
|
|
for (i = 0; i < tz->trips; i++) {
|
|
|
|
if (mask & (1 << i)) {
|
2013-07-16 23:26:28 +04:00
|
|
|
unsigned long upper, lower;
|
|
|
|
|
|
|
|
upper = THERMAL_NO_LIMIT;
|
|
|
|
lower = THERMAL_NO_LIMIT;
|
|
|
|
if (limits) {
|
|
|
|
lower = limits[i * 2];
|
|
|
|
upper = limits[i * 2 + 1];
|
|
|
|
}
|
2012-09-18 09:34:59 +04:00
|
|
|
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
|
2015-02-18 19:04:21 +03:00
|
|
|
upper, lower,
|
|
|
|
weight);
|
2012-09-18 09:34:59 +04:00
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(tz, cdev, ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __unbind(struct thermal_zone_device *tz, int mask,
|
|
|
|
struct thermal_cooling_device *cdev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < tz->trips; i++)
|
|
|
|
if (mask & (1 << i))
|
|
|
|
thermal_zone_unbind_cooling_device(tz, i, cdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bind_cdev(struct thermal_cooling_device *cdev)
|
|
|
|
{
|
|
|
|
int i, ret;
|
|
|
|
const struct thermal_zone_params *tzp;
|
|
|
|
struct thermal_zone_device *pos = NULL;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
|
|
|
if (!pos->tzp && !pos->ops->bind)
|
|
|
|
continue;
|
|
|
|
|
2013-11-06 10:30:13 +04:00
|
|
|
if (pos->ops->bind) {
|
2012-09-18 09:34:59 +04:00
|
|
|
ret = pos->ops->bind(pos, cdev);
|
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(pos, cdev, ret);
|
2013-11-06 10:30:13 +04:00
|
|
|
continue;
|
2012-09-18 09:34:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
tzp = pos->tzp;
|
2012-09-28 03:48:28 +04:00
|
|
|
if (!tzp || !tzp->tbp)
|
|
|
|
continue;
|
2012-09-18 09:34:59 +04:00
|
|
|
|
|
|
|
for (i = 0; i < tzp->num_tbps; i++) {
|
|
|
|
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
|
|
|
|
continue;
|
|
|
|
if (tzp->tbp[i].match(pos, cdev))
|
|
|
|
continue;
|
|
|
|
tzp->tbp[i].cdev = cdev;
|
2013-07-16 23:26:28 +04:00
|
|
|
__bind(pos, tzp->tbp[i].trip_mask, cdev,
|
2015-02-18 19:04:21 +03:00
|
|
|
tzp->tbp[i].binding_limits,
|
|
|
|
tzp->tbp[i].weight);
|
2012-09-18 09:34:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bind_tz(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
int i, ret;
|
|
|
|
struct thermal_cooling_device *pos = NULL;
|
|
|
|
const struct thermal_zone_params *tzp = tz->tzp;
|
|
|
|
|
|
|
|
if (!tzp && !tz->ops->bind)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
2013-11-06 10:30:13 +04:00
|
|
|
/* If there is ops->bind, try to use ops->bind */
|
|
|
|
if (tz->ops->bind) {
|
2012-09-18 09:34:59 +04:00
|
|
|
list_for_each_entry(pos, &thermal_cdev_list, node) {
|
|
|
|
ret = tz->ops->bind(tz, pos);
|
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(tz, pos, ret);
|
|
|
|
}
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2012-09-28 03:48:28 +04:00
|
|
|
if (!tzp || !tzp->tbp)
|
2012-09-18 09:34:59 +04:00
|
|
|
goto exit;
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_cdev_list, node) {
|
|
|
|
for (i = 0; i < tzp->num_tbps; i++) {
|
|
|
|
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
|
|
|
|
continue;
|
|
|
|
if (tzp->tbp[i].match(tz, pos))
|
|
|
|
continue;
|
|
|
|
tzp->tbp[i].cdev = pos;
|
2013-07-16 23:26:28 +04:00
|
|
|
__bind(tz, tzp->tbp[i].trip_mask, pos,
|
2015-02-18 19:04:21 +03:00
|
|
|
tzp->tbp[i].binding_limits,
|
|
|
|
tzp->tbp[i].weight);
|
2012-09-18 09:34:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
}
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
|
|
|
|
int delay)
|
|
|
|
{
|
|
|
|
if (delay > 1000)
|
|
|
|
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
|
|
|
|
round_jiffies(msecs_to_jiffies(delay)));
|
|
|
|
else if (delay)
|
|
|
|
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
|
|
|
|
msecs_to_jiffies(delay));
|
|
|
|
else
|
|
|
|
cancel_delayed_work(&tz->poll_queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void monitor_thermal_zone(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
if (tz->passive)
|
|
|
|
thermal_zone_device_set_polling(tz, tz->passive_delay);
|
|
|
|
else if (tz->polling_delay)
|
|
|
|
thermal_zone_device_set_polling(tz, tz->polling_delay);
|
|
|
|
else
|
|
|
|
thermal_zone_device_set_polling(tz, 0);
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_non_critical_trips(struct thermal_zone_device *tz,
|
|
|
|
int trip, enum thermal_trip_type trip_type)
|
|
|
|
{
|
2014-01-24 06:23:19 +04:00
|
|
|
tz->governor ? tz->governor->throttle(tz, trip) :
|
|
|
|
def_governor->throttle(tz, trip);
|
2012-09-18 09:35:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_critical_trips(struct thermal_zone_device *tz,
|
|
|
|
int trip, enum thermal_trip_type trip_type)
|
|
|
|
{
|
2015-07-24 09:12:54 +03:00
|
|
|
int trip_temp;
|
2012-09-18 09:35:04 +04:00
|
|
|
|
|
|
|
tz->ops->get_trip_temp(tz, trip, &trip_temp);
|
|
|
|
|
|
|
|
/* If we have not crossed the trip_temp, we do not care. */
|
2014-11-13 02:43:29 +03:00
|
|
|
if (trip_temp <= 0 || tz->temperature < trip_temp)
|
2012-09-18 09:35:04 +04:00
|
|
|
return;
|
|
|
|
|
2014-07-29 14:50:50 +04:00
|
|
|
trace_thermal_zone_trip(tz, trip, trip_type);
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
if (tz->ops->notify)
|
|
|
|
tz->ops->notify(tz, trip, trip_type);
|
|
|
|
|
|
|
|
if (trip_type == THERMAL_TRIP_CRITICAL) {
|
2013-01-02 19:29:41 +04:00
|
|
|
dev_emerg(&tz->device,
|
|
|
|
"critical temperature reached(%d C),shutting down\n",
|
|
|
|
tz->temperature / 1000);
|
2012-09-18 09:35:04 +04:00
|
|
|
orderly_poweroff(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
|
|
|
|
{
|
|
|
|
enum thermal_trip_type type;
|
|
|
|
|
2016-03-18 05:03:24 +03:00
|
|
|
/* Ignore disabled trip points */
|
|
|
|
if (test_bit(trip, &tz->trips_disabled))
|
|
|
|
return;
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
tz->ops->get_trip_type(tz, trip, &type);
|
|
|
|
|
|
|
|
if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)
|
|
|
|
handle_critical_trips(tz, trip, type);
|
|
|
|
else
|
|
|
|
handle_non_critical_trips(tz, trip, type);
|
|
|
|
/*
|
|
|
|
* Alright, we handled this trip successfully.
|
|
|
|
* So, start monitoring again.
|
|
|
|
*/
|
|
|
|
monitor_thermal_zone(tz);
|
|
|
|
}
|
|
|
|
|
2013-04-05 16:32:29 +04:00
|
|
|
/**
|
2015-07-06 10:46:14 +03:00
|
|
|
* thermal_zone_get_temp() - returns the temperature of a thermal zone
|
2013-04-05 16:32:29 +04:00
|
|
|
* @tz: a valid pointer to a struct thermal_zone_device
|
|
|
|
* @temp: a valid pointer to where to store the resulting temperature.
|
|
|
|
*
|
|
|
|
* When a valid thermal zone reference is passed, it will fetch its
|
|
|
|
* temperature and fill @temp.
|
|
|
|
*
|
|
|
|
* Return: On success returns 0, an error code otherwise
|
|
|
|
*/
|
2015-07-24 09:12:54 +03:00
|
|
|
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
|
2013-02-04 04:30:15 +04:00
|
|
|
{
|
2013-04-05 16:32:29 +04:00
|
|
|
int ret = -EINVAL;
|
2013-02-06 10:02:12 +04:00
|
|
|
int count;
|
2015-07-24 09:12:54 +03:00
|
|
|
int crit_temp = INT_MAX;
|
2013-02-04 04:30:15 +04:00
|
|
|
enum thermal_trip_type type;
|
|
|
|
|
2013-09-13 03:15:44 +04:00
|
|
|
if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
|
2013-04-05 16:32:29 +04:00
|
|
|
goto exit;
|
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
ret = tz->ops->get_temp(tz, temp);
|
|
|
|
|
2015-07-06 10:46:16 +03:00
|
|
|
if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
|
|
|
|
for (count = 0; count < tz->trips; count++) {
|
|
|
|
ret = tz->ops->get_trip_type(tz, count, &type);
|
|
|
|
if (!ret && type == THERMAL_TRIP_CRITICAL) {
|
|
|
|
ret = tz->ops->get_trip_temp(tz, count,
|
|
|
|
&crit_temp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-02-04 04:30:15 +04:00
|
|
|
|
2015-07-06 10:46:17 +03:00
|
|
|
/*
|
|
|
|
* Only allow emulating a temperature when the real temperature
|
|
|
|
* is below the critical temperature so that the emulation code
|
|
|
|
* cannot hide critical conditions.
|
|
|
|
*/
|
2015-07-06 10:46:16 +03:00
|
|
|
if (!ret && *temp < crit_temp)
|
|
|
|
*temp = tz->emul_temperature;
|
|
|
|
}
|
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
mutex_unlock(&tz->lock);
|
2013-04-05 16:32:29 +04:00
|
|
|
exit:
|
2013-02-04 04:30:15 +04:00
|
|
|
return ret;
|
|
|
|
}
|
2013-04-05 16:32:29 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
|
2013-02-04 04:30:15 +04:00
|
|
|
|
2016-06-22 11:42:01 +03:00
|
|
|
void thermal_zone_set_trips(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
int low = -INT_MAX;
|
|
|
|
int high = INT_MAX;
|
|
|
|
int trip_temp, hysteresis;
|
|
|
|
int i, ret;
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
for (i = 0; i < tz->trips; i++) {
|
|
|
|
int trip_low;
|
|
|
|
|
|
|
|
tz->ops->get_trip_temp(tz, i, &trip_temp);
|
|
|
|
tz->ops->get_trip_hyst(tz, i, &hysteresis);
|
|
|
|
|
|
|
|
trip_low = trip_temp - hysteresis;
|
|
|
|
|
|
|
|
if (trip_low < tz->temperature && trip_low > low)
|
|
|
|
low = trip_low;
|
|
|
|
|
|
|
|
if (trip_temp > tz->temperature && trip_temp < high)
|
|
|
|
high = trip_temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No need to change trip points */
|
|
|
|
if (tz->prev_low_trip == low && tz->prev_high_trip == high)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
tz->prev_low_trip = low;
|
|
|
|
tz->prev_high_trip = high;
|
|
|
|
|
|
|
|
dev_dbg(&tz->device,
|
|
|
|
"new temperature boundaries: %d < x < %d\n", low, high);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set a temperature window. When this window is left the driver
|
|
|
|
* must inform the thermal core via thermal_zone_device_update.
|
|
|
|
*/
|
|
|
|
ret = tz->ops->set_trips(tz, low, high);
|
|
|
|
if (ret)
|
|
|
|
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_set_trips);
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
static void update_temperature(struct thermal_zone_device *tz)
|
|
|
|
{
|
2015-07-24 09:12:54 +03:00
|
|
|
int temp, ret;
|
2012-09-18 09:35:04 +04:00
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
ret = thermal_zone_get_temp(tz, &temp);
|
2012-09-18 09:35:04 +04:00
|
|
|
if (ret) {
|
2015-03-21 17:02:55 +03:00
|
|
|
if (ret != -EAGAIN)
|
|
|
|
dev_warn(&tz->device,
|
|
|
|
"failed to read out thermal zone (%d)\n",
|
|
|
|
ret);
|
2013-02-04 04:30:15 +04:00
|
|
|
return;
|
2012-09-18 09:35:04 +04:00
|
|
|
}
|
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
mutex_lock(&tz->lock);
|
2012-09-18 09:35:04 +04:00
|
|
|
tz->last_temperature = tz->temperature;
|
|
|
|
tz->temperature = temp;
|
|
|
|
mutex_unlock(&tz->lock);
|
thermal: debug: add debug statement for core and step_wise
To ease debugging thermal problem, add these dynamic debug statements
so that user do not need rebuild kernel to see these info.
Based on a patch from Zhang Rui for debugging on bugzilla:
https://bugzilla.kernel.org/attachment.cgi?id=98671
A sample output after we turn on dynamic debug with the following cmd:
# echo 'module thermal_sys +fp' > /sys/kernel/debug/dynamic_debug/control
is like:
[ 355.147627] update_temperature: thermal thermal_zone0: last_temperature=52000, current_temperature=55000
[ 355.147636] thermal_zone_trip_update: thermal thermal_zone0: Trip1[type=1,temp=79000]:trend=2,throttle=0
[ 355.147644] get_target_state: thermal cooling_device8: cur_state=0
[ 355.147647] thermal_zone_trip_update: thermal cooling_device8: old_target=-1, target=-1
[ 355.147652] get_target_state: thermal cooling_device7: cur_state=0
[ 355.147655] thermal_zone_trip_update: thermal cooling_device7: old_target=-1, target=-1
[ 355.147660] get_target_state: thermal cooling_device6: cur_state=0
[ 355.147663] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 355.147668] get_target_state: thermal cooling_device5: cur_state=0
[ 355.147671] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 355.147678] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=90000]:trend=1,throttle=0
[ 355.147776] get_target_state: thermal cooling_device0: cur_state=0
[ 355.147783] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[ 355.147792] thermal_zone_trip_update: thermal thermal_zone0: Trip3[type=0,temp=80000]:trend=1,throttle=0
[ 355.147845] get_target_state: thermal cooling_device1: cur_state=0
[ 355.147849] thermal_zone_trip_update: thermal cooling_device1: old_target=-1, target=-1
[ 355.147856] thermal_zone_trip_update: thermal thermal_zone0: Trip4[type=0,temp=70000]:trend=1,throttle=0
[ 355.147904] get_target_state: thermal cooling_device2: cur_state=0
[ 355.147908] thermal_zone_trip_update: thermal cooling_device2: old_target=-1, target=-1
[ 355.147915] thermal_zone_trip_update: thermal thermal_zone0: Trip5[type=0,temp=60000]:trend=1,throttle=0
[ 355.147963] get_target_state: thermal cooling_device3: cur_state=0
[ 355.147967] thermal_zone_trip_update: thermal cooling_device3: old_target=-1, target=-1
[ 355.147973] thermal_zone_trip_update: thermal thermal_zone0: Trip6[type=0,temp=55000]:trend=1,throttle=1
[ 355.148022] get_target_state: thermal cooling_device4: cur_state=0
[ 355.148025] thermal_zone_trip_update: thermal cooling_device4: old_target=-1, target=1
[ 355.148036] thermal_cdev_update: thermal cooling_device4: zone0->target=1
[ 355.169279] thermal_cdev_update: thermal cooling_device4: set to state 1
Signed-off-by: Aaron Lu <aaron.lu@intel.com>
Acked-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2013-12-02 09:54:26 +04:00
|
|
|
|
2014-07-29 14:50:48 +04:00
|
|
|
trace_thermal_temperature(tz);
|
2015-10-30 11:31:47 +03:00
|
|
|
if (tz->last_temperature == THERMAL_TEMP_INVALID)
|
|
|
|
dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n",
|
|
|
|
tz->temperature);
|
|
|
|
else
|
|
|
|
dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
|
|
|
|
tz->last_temperature, tz->temperature);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void thermal_zone_device_reset(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
struct thermal_instance *pos;
|
|
|
|
|
|
|
|
tz->temperature = THERMAL_TEMP_INVALID;
|
|
|
|
tz->passive = 0;
|
|
|
|
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
|
|
|
|
pos->initialized = false;
|
2012-09-18 09:35:04 +04:00
|
|
|
}
|
|
|
|
|
2016-08-27 02:21:16 +03:00
|
|
|
void thermal_zone_device_update(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_notify_event event)
|
2012-09-18 09:35:04 +04:00
|
|
|
{
|
|
|
|
int count;
|
|
|
|
|
2015-10-30 11:31:58 +03:00
|
|
|
if (atomic_read(&in_suspend))
|
|
|
|
return;
|
|
|
|
|
2013-09-13 03:15:44 +04:00
|
|
|
if (!tz->ops->get_temp)
|
|
|
|
return;
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
update_temperature(tz);
|
|
|
|
|
2016-06-22 11:42:01 +03:00
|
|
|
thermal_zone_set_trips(tz);
|
|
|
|
|
2016-08-27 02:21:16 +03:00
|
|
|
tz->notify_event = event;
|
|
|
|
|
2012-09-18 09:35:04 +04:00
|
|
|
for (count = 0; count < tz->trips; count++)
|
|
|
|
handle_thermal_trip(tz, count);
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_update);
|
2012-09-18 09:35:04 +04:00
|
|
|
|
|
|
|
static void thermal_zone_device_check(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = container_of(work, struct
|
|
|
|
thermal_zone_device,
|
|
|
|
poll_queue.work);
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2012-09-18 09:35:04 +04:00
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:47 +03:00
|
|
|
/**
|
|
|
|
* power_actor_get_max_power() - get the maximum power that a cdev can consume
|
|
|
|
* @cdev: pointer to &thermal_cooling_device
|
|
|
|
* @tz: a valid thermal zone device pointer
|
|
|
|
* @max_power: pointer in which to store the maximum power
|
|
|
|
*
|
|
|
|
* Calculate the maximum power consumption in milliwats that the
|
|
|
|
* cooling device can currently consume and store it in @max_power.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, -EINVAL if @cdev doesn't support the
|
|
|
|
* power_actor API or -E* on other error.
|
|
|
|
*/
|
|
|
|
int power_actor_get_max_power(struct thermal_cooling_device *cdev,
|
|
|
|
struct thermal_zone_device *tz, u32 *max_power)
|
|
|
|
{
|
|
|
|
if (!cdev_is_power_actor(cdev))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return cdev->ops->state2power(cdev, tz, 0, max_power);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* power_actor_get_min_power() - get the mainimum power that a cdev can consume
|
|
|
|
* @cdev: pointer to &thermal_cooling_device
|
|
|
|
* @tz: a valid thermal zone device pointer
|
|
|
|
* @min_power: pointer in which to store the minimum power
|
|
|
|
*
|
|
|
|
* Calculate the minimum power consumption in milliwatts that the
|
|
|
|
* cooling device can currently consume and store it in @min_power.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, -EINVAL if @cdev doesn't support the
|
|
|
|
* power_actor API or -E* on other error.
|
|
|
|
*/
|
|
|
|
int power_actor_get_min_power(struct thermal_cooling_device *cdev,
|
|
|
|
struct thermal_zone_device *tz, u32 *min_power)
|
|
|
|
{
|
|
|
|
unsigned long max_state;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!cdev_is_power_actor(cdev))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = cdev->ops->get_max_state(cdev, &max_state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return cdev->ops->state2power(cdev, tz, max_state, min_power);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* power_actor_set_power() - limit the maximum power a cooling device consumes
|
|
|
|
* @cdev: pointer to &thermal_cooling_device
|
|
|
|
* @instance: thermal instance to update
|
|
|
|
* @power: the power in milliwatts
|
|
|
|
*
|
|
|
|
* Set the cooling device to consume at most @power milliwatts. The limit is
|
|
|
|
* expected to be a cap at the maximum power consumption.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, -EINVAL if the cooling device does not
|
|
|
|
* implement the power actor API or -E* for other failures.
|
|
|
|
*/
|
|
|
|
int power_actor_set_power(struct thermal_cooling_device *cdev,
|
|
|
|
struct thermal_instance *instance, u32 power)
|
|
|
|
{
|
|
|
|
unsigned long state;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!cdev_is_power_actor(cdev))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = cdev->ops->power2state(cdev, instance->tz, power, &state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
instance->target = state;
|
|
|
|
mutex_lock(&cdev->lock);
|
|
|
|
cdev->updated = false;
|
|
|
|
mutex_unlock(&cdev->lock);
|
|
|
|
thermal_cdev_update(cdev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
/* sys I/F for thermal zone */
|
|
|
|
|
|
|
|
#define to_thermal_zone(_dev) \
|
|
|
|
container_of(_dev, struct thermal_zone_device, device)
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
type_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", tz->type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
temp_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
2015-07-24 09:12:54 +03:00
|
|
|
int temperature, ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
ret = thermal_zone_get_temp(tz, &temperature);
|
2008-11-27 20:48:13 +03:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-07-24 09:12:54 +03:00
|
|
|
return sprintf(buf, "%d\n", temperature);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
enum thermal_device_mode mode;
|
|
|
|
int result;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (!tz->ops->get_mode)
|
|
|
|
return -EPERM;
|
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
result = tz->ops->get_mode(tz, &mode);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
|
|
|
|
: "disabled");
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
mode_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
int result;
|
|
|
|
|
|
|
|
if (!tz->ops->set_mode)
|
|
|
|
return -EPERM;
|
|
|
|
|
2012-03-21 15:10:01 +04:00
|
|
|
if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
|
2008-11-27 20:48:13 +03:00
|
|
|
result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
|
2012-03-21 15:10:01 +04:00
|
|
|
else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
|
2008-11-27 20:48:13 +03:00
|
|
|
result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
|
|
|
|
else
|
|
|
|
result = -EINVAL;
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
trip_point_type_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
enum thermal_trip_type type;
|
|
|
|
int trip, result;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (!tz->ops->get_trip_type)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
result = tz->ops->get_trip_type(tz, trip, &type);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case THERMAL_TRIP_CRITICAL:
|
2009-10-16 13:46:02 +04:00
|
|
|
return sprintf(buf, "critical\n");
|
2008-11-27 20:48:13 +03:00
|
|
|
case THERMAL_TRIP_HOT:
|
2009-10-16 13:46:02 +04:00
|
|
|
return sprintf(buf, "hot\n");
|
2008-11-27 20:48:13 +03:00
|
|
|
case THERMAL_TRIP_PASSIVE:
|
2009-10-16 13:46:02 +04:00
|
|
|
return sprintf(buf, "passive\n");
|
2008-11-27 20:48:13 +03:00
|
|
|
case THERMAL_TRIP_ACTIVE:
|
2009-10-16 13:46:02 +04:00
|
|
|
return sprintf(buf, "active\n");
|
2008-11-27 20:48:13 +03:00
|
|
|
default:
|
2009-10-16 13:46:02 +04:00
|
|
|
return sprintf(buf, "unknown\n");
|
2008-11-27 20:48:13 +03:00
|
|
|
}
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2012-07-25 06:10:58 +04:00
|
|
|
static ssize_t
|
|
|
|
trip_point_temp_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
int trip, ret;
|
2016-03-03 12:33:46 +03:00
|
|
|
int temperature;
|
2012-07-25 06:10:58 +04:00
|
|
|
|
|
|
|
if (!tz->ops->set_trip_temp)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2016-03-03 12:33:46 +03:00
|
|
|
if (kstrtoint(buf, 10, &temperature))
|
2012-07-25 06:10:58 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = tz->ops->set_trip_temp(tz, trip, temperature);
|
2015-12-15 04:19:53 +03:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2012-07-25 06:10:58 +04:00
|
|
|
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2015-12-15 04:19:53 +03:00
|
|
|
|
|
|
|
return count;
|
2012-07-25 06:10:58 +04:00
|
|
|
}
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
static ssize_t
|
|
|
|
trip_point_temp_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
int trip, ret;
|
2015-07-24 09:12:54 +03:00
|
|
|
int temperature;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (!tz->ops->get_trip_temp)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
ret = tz->ops->get_trip_temp(tz, trip, &temperature);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-07-24 09:12:54 +03:00
|
|
|
return sprintf(buf, "%d\n", temperature);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2012-07-25 06:10:59 +04:00
|
|
|
static ssize_t
|
|
|
|
trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
int trip, ret;
|
2015-07-24 09:12:54 +03:00
|
|
|
int temperature;
|
2012-07-25 06:10:59 +04:00
|
|
|
|
|
|
|
if (!tz->ops->set_trip_hyst)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2015-07-24 09:12:54 +03:00
|
|
|
if (kstrtoint(buf, 10, &temperature))
|
2012-07-25 06:10:59 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We are not doing any check on the 'temperature' value
|
|
|
|
* here. The driver implementing 'set_trip_hyst' has to
|
|
|
|
* take care of this.
|
|
|
|
*/
|
|
|
|
ret = tz->ops->set_trip_hyst(tz, trip, temperature);
|
|
|
|
|
2016-06-22 11:42:01 +03:00
|
|
|
if (!ret)
|
|
|
|
thermal_zone_set_trips(tz);
|
|
|
|
|
2012-07-25 06:10:59 +04:00
|
|
|
return ret ? ret : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
int trip, ret;
|
2015-07-24 09:12:54 +03:00
|
|
|
int temperature;
|
2012-07-25 06:10:59 +04:00
|
|
|
|
|
|
|
if (!tz->ops->get_trip_hyst)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
|
|
|
|
|
2015-07-24 09:12:54 +03:00
|
|
|
return ret ? ret : sprintf(buf, "%d\n", temperature);
|
2012-07-25 06:10:59 +04:00
|
|
|
}
|
|
|
|
|
2008-12-03 21:00:38 +03:00
|
|
|
static ssize_t
|
|
|
|
passive_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
struct thermal_cooling_device *cdev = NULL;
|
|
|
|
int state;
|
|
|
|
|
|
|
|
if (!sscanf(buf, "%d\n", &state))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-10-26 10:39:02 +03:00
|
|
|
/* sanity check: values below 1000 millicelcius don't make sense
|
|
|
|
* and can cause the system to go into a thermal heart attack
|
|
|
|
*/
|
|
|
|
if (state && state < 1000)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-12-03 21:00:38 +03:00
|
|
|
if (state && !tz->forced_passive) {
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node) {
|
|
|
|
if (!strncmp("Processor", cdev->type,
|
|
|
|
sizeof("Processor")))
|
|
|
|
thermal_zone_bind_cooling_device(tz,
|
2012-06-26 12:35:57 +04:00
|
|
|
THERMAL_TRIPS_NONE, cdev,
|
|
|
|
THERMAL_NO_LIMIT,
|
2015-02-18 19:04:21 +03:00
|
|
|
THERMAL_NO_LIMIT,
|
|
|
|
THERMAL_WEIGHT_DEFAULT);
|
2008-12-03 21:00:38 +03:00
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
2009-10-26 10:39:03 +03:00
|
|
|
if (!tz->passive_delay)
|
|
|
|
tz->passive_delay = 1000;
|
2008-12-03 21:00:38 +03:00
|
|
|
} else if (!state && tz->forced_passive) {
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node) {
|
|
|
|
if (!strncmp("Processor", cdev->type,
|
|
|
|
sizeof("Processor")))
|
|
|
|
thermal_zone_unbind_cooling_device(tz,
|
|
|
|
THERMAL_TRIPS_NONE,
|
|
|
|
cdev);
|
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
2009-10-26 10:39:03 +03:00
|
|
|
tz->passive_delay = 0;
|
2008-12-03 21:00:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
tz->forced_passive = state;
|
|
|
|
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2008-12-03 21:00:38 +03:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
passive_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%d\n", tz->forced_passive);
|
|
|
|
}
|
|
|
|
|
2012-09-18 09:34:58 +04:00
|
|
|
static ssize_t
|
|
|
|
policy_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
int ret = -EINVAL;
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
struct thermal_governor *gov;
|
2013-05-17 15:52:02 +04:00
|
|
|
char name[THERMAL_NAME_LENGTH];
|
|
|
|
|
|
|
|
snprintf(name, sizeof(name), "%s", buf);
|
2012-09-18 09:34:58 +04:00
|
|
|
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
2014-11-25 19:00:33 +03:00
|
|
|
mutex_lock(&tz->lock);
|
2012-09-18 09:34:58 +04:00
|
|
|
|
2013-05-17 15:52:02 +04:00
|
|
|
gov = __find_governor(strim(name));
|
2012-09-18 09:34:58 +04:00
|
|
|
if (!gov)
|
|
|
|
goto exit;
|
|
|
|
|
2015-02-26 22:00:27 +03:00
|
|
|
ret = thermal_set_governor(tz, gov);
|
|
|
|
if (!ret)
|
|
|
|
ret = count;
|
2012-09-18 09:34:58 +04:00
|
|
|
|
|
|
|
exit:
|
2014-11-25 19:00:33 +03:00
|
|
|
mutex_unlock(&tz->lock);
|
2012-09-18 09:34:58 +04:00
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", tz->governor->name);
|
|
|
|
}
|
|
|
|
|
2015-07-14 10:40:56 +03:00
|
|
|
static ssize_t
|
|
|
|
available_policies_show(struct device *dev, struct device_attribute *devattr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_governor *pos;
|
|
|
|
ssize_t count = 0;
|
|
|
|
ssize_t size = PAGE_SIZE;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_governor_list, governor_list) {
|
|
|
|
size = PAGE_SIZE - count;
|
|
|
|
count += scnprintf(buf + count, size, "%s ", pos->name);
|
|
|
|
}
|
|
|
|
count += scnprintf(buf + count, size, "\n");
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
static ssize_t
|
|
|
|
emul_temp_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
int ret = 0;
|
2016-03-03 12:33:46 +03:00
|
|
|
int temperature;
|
2013-02-04 04:30:15 +04:00
|
|
|
|
2016-03-03 12:33:46 +03:00
|
|
|
if (kstrtoint(buf, 10, &temperature))
|
2013-02-04 04:30:15 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!tz->ops->set_emul_temp) {
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
tz->emul_temperature = temperature;
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
} else {
|
|
|
|
ret = tz->ops->set_emul_temp(tz, temperature);
|
|
|
|
}
|
|
|
|
|
2014-01-02 11:47:54 +04:00
|
|
|
if (!ret)
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2014-01-02 11:47:54 +04:00
|
|
|
|
2013-02-04 04:30:15 +04:00
|
|
|
return ret ? ret : count;
|
|
|
|
}
|
|
|
|
|
2015-03-26 18:53:02 +03:00
|
|
|
static ssize_t
|
|
|
|
sustainable_power_show(struct device *dev, struct device_attribute *devattr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
|
|
|
|
if (tz->tzp)
|
|
|
|
return sprintf(buf, "%u\n", tz->tzp->sustainable_power);
|
|
|
|
else
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
sustainable_power_store(struct device *dev, struct device_attribute *devattr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
|
|
|
u32 sustainable_power;
|
|
|
|
|
|
|
|
if (!tz->tzp)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
if (kstrtou32(buf, 10, &sustainable_power))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tz->tzp->sustainable_power = sustainable_power;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define create_s32_tzp_attr(name) \
|
|
|
|
static ssize_t \
|
|
|
|
name##_show(struct device *dev, struct device_attribute *devattr, \
|
|
|
|
char *buf) \
|
|
|
|
{ \
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev); \
|
|
|
|
\
|
|
|
|
if (tz->tzp) \
|
2016-03-29 14:24:15 +03:00
|
|
|
return sprintf(buf, "%d\n", tz->tzp->name); \
|
2015-03-26 18:53:02 +03:00
|
|
|
else \
|
|
|
|
return -EIO; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
static ssize_t \
|
|
|
|
name##_store(struct device *dev, struct device_attribute *devattr, \
|
|
|
|
const char *buf, size_t count) \
|
|
|
|
{ \
|
|
|
|
struct thermal_zone_device *tz = to_thermal_zone(dev); \
|
|
|
|
s32 value; \
|
|
|
|
\
|
|
|
|
if (!tz->tzp) \
|
|
|
|
return -EIO; \
|
|
|
|
\
|
|
|
|
if (kstrtos32(buf, 10, &value)) \
|
|
|
|
return -EINVAL; \
|
|
|
|
\
|
|
|
|
tz->tzp->name = value; \
|
|
|
|
\
|
|
|
|
return count; \
|
|
|
|
} \
|
|
|
|
static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, name##_show, name##_store)
|
|
|
|
|
|
|
|
create_s32_tzp_attr(k_po);
|
|
|
|
create_s32_tzp_attr(k_pu);
|
|
|
|
create_s32_tzp_attr(k_i);
|
|
|
|
create_s32_tzp_attr(k_d);
|
|
|
|
create_s32_tzp_attr(integral_cutoff);
|
2015-05-12 05:34:23 +03:00
|
|
|
create_s32_tzp_attr(slope);
|
|
|
|
create_s32_tzp_attr(offset);
|
2015-03-26 18:53:02 +03:00
|
|
|
#undef create_s32_tzp_attr
|
|
|
|
|
2016-11-08 08:08:42 +03:00
|
|
|
/*
|
|
|
|
* These are thermal zone device attributes that will always be present.
|
|
|
|
* All the attributes created for tzp (create_s32_tzp_attr) also are always
|
|
|
|
* present on the sysfs interface.
|
|
|
|
*/
|
2016-11-08 08:08:40 +03:00
|
|
|
static DEVICE_ATTR(type, 0444, type_show, NULL);
|
|
|
|
static DEVICE_ATTR(temp, 0444, temp_show, NULL);
|
2016-11-08 08:08:43 +03:00
|
|
|
static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
|
2016-11-08 08:08:40 +03:00
|
|
|
static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
|
|
|
|
static DEVICE_ATTR(available_policies, S_IRUGO, available_policies_show, NULL);
|
|
|
|
static DEVICE_ATTR(sustainable_power, S_IWUSR | S_IRUGO, sustainable_power_show,
|
|
|
|
sustainable_power_store);
|
|
|
|
|
2016-11-08 08:08:42 +03:00
|
|
|
/* These thermal zone device attributes are created based on conditions */
|
|
|
|
static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
|
|
|
|
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
|
2015-03-26 18:53:02 +03:00
|
|
|
|
2016-11-08 08:08:44 +03:00
|
|
|
/* These attributes are unconditionally added to a thermal zone */
|
2016-11-08 08:08:42 +03:00
|
|
|
static struct attribute *thermal_zone_dev_attrs[] = {
|
|
|
|
&dev_attr_type.attr,
|
|
|
|
&dev_attr_temp.attr,
|
2016-11-08 08:08:43 +03:00
|
|
|
#if (IS_ENABLED(CONFIG_THERMAL_EMULATION))
|
|
|
|
&dev_attr_emul_temp.attr,
|
|
|
|
#endif
|
2016-11-08 08:08:42 +03:00
|
|
|
&dev_attr_policy.attr,
|
|
|
|
&dev_attr_available_policies.attr,
|
|
|
|
&dev_attr_sustainable_power.attr,
|
|
|
|
&dev_attr_k_po.attr,
|
|
|
|
&dev_attr_k_pu.attr,
|
|
|
|
&dev_attr_k_i.attr,
|
|
|
|
&dev_attr_k_d.attr,
|
|
|
|
&dev_attr_integral_cutoff.attr,
|
|
|
|
&dev_attr_slope.attr,
|
|
|
|
&dev_attr_offset.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
2015-03-26 18:53:02 +03:00
|
|
|
|
2016-11-08 08:08:42 +03:00
|
|
|
static struct attribute_group thermal_zone_attribute_group = {
|
|
|
|
.attrs = thermal_zone_dev_attrs,
|
|
|
|
};
|
2015-03-26 18:53:02 +03:00
|
|
|
|
2016-11-08 08:08:44 +03:00
|
|
|
/* We expose mode only if .get_mode is present */
|
|
|
|
static struct attribute *thermal_zone_mode_attrs[] = {
|
|
|
|
&dev_attr_mode.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static umode_t thermal_zone_mode_is_visible(struct kobject *kobj,
|
|
|
|
struct attribute *attr,
|
|
|
|
int attrno)
|
|
|
|
{
|
|
|
|
struct device *dev = container_of(kobj, struct device, kobj);
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
|
|
|
|
tz = container_of(dev, struct thermal_zone_device, device);
|
|
|
|
|
|
|
|
if (tz->ops->get_mode)
|
|
|
|
return attr->mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct attribute_group thermal_zone_mode_attribute_group = {
|
|
|
|
.attrs = thermal_zone_mode_attrs,
|
|
|
|
.is_visible = thermal_zone_mode_is_visible,
|
|
|
|
};
|
|
|
|
|
2016-11-08 08:08:45 +03:00
|
|
|
/* We expose passive only if passive trips are present */
|
|
|
|
static struct attribute *thermal_zone_passive_attrs[] = {
|
|
|
|
&dev_attr_passive.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static umode_t thermal_zone_passive_is_visible(struct kobject *kobj,
|
|
|
|
struct attribute *attr,
|
|
|
|
int attrno)
|
|
|
|
{
|
|
|
|
struct device *dev = container_of(kobj, struct device, kobj);
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
enum thermal_trip_type trip_type;
|
|
|
|
int count, passive = 0;
|
|
|
|
|
|
|
|
tz = container_of(dev, struct thermal_zone_device, device);
|
|
|
|
|
|
|
|
for (count = 0; count < tz->trips && !passive; count++) {
|
|
|
|
tz->ops->get_trip_type(tz, count, &trip_type);
|
|
|
|
|
|
|
|
if (trip_type == THERMAL_TRIP_PASSIVE)
|
|
|
|
passive = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!passive)
|
|
|
|
return attr->mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct attribute_group thermal_zone_passive_attribute_group = {
|
|
|
|
.attrs = thermal_zone_passive_attrs,
|
|
|
|
.is_visible = thermal_zone_passive_is_visible,
|
|
|
|
};
|
|
|
|
|
2016-11-08 08:08:42 +03:00
|
|
|
static const struct attribute_group *thermal_zone_attribute_groups[] = {
|
|
|
|
&thermal_zone_attribute_group,
|
2016-11-08 08:08:44 +03:00
|
|
|
&thermal_zone_mode_attribute_group,
|
2016-11-08 08:08:45 +03:00
|
|
|
&thermal_zone_passive_attribute_group,
|
2016-11-08 08:08:51 +03:00
|
|
|
/* This is not NULL terminated as we create the group dynamically */
|
2016-11-08 08:08:42 +03:00
|
|
|
};
|
2015-03-26 18:53:02 +03:00
|
|
|
|
2016-11-08 08:08:50 +03:00
|
|
|
/**
|
|
|
|
* create_trip_attrs() - create attributes for trip points
|
|
|
|
* @tz: the thermal zone device
|
|
|
|
* @mask: Writeable trip point bitmap.
|
|
|
|
*
|
|
|
|
* helper function to instantiate sysfs entries for every trip
|
|
|
|
* point and its properties of a struct thermal_zone_device.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, the proper error value otherwise.
|
|
|
|
*/
|
|
|
|
static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
|
|
|
|
{
|
|
|
|
int size = sizeof(struct thermal_attr) * tz->trips;
|
2016-11-08 08:08:52 +03:00
|
|
|
struct attribute **attrs;
|
|
|
|
int indx;
|
|
|
|
|
|
|
|
/* This function works only for zones with at least one trip */
|
|
|
|
if (tz->trips <= 0)
|
|
|
|
return -EINVAL;
|
2016-11-08 08:08:50 +03:00
|
|
|
|
|
|
|
tz->trip_type_attrs = kzalloc(size, GFP_KERNEL);
|
|
|
|
if (!tz->trip_type_attrs)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
tz->trip_temp_attrs = kzalloc(size, GFP_KERNEL);
|
|
|
|
if (!tz->trip_temp_attrs) {
|
|
|
|
kfree(tz->trip_type_attrs);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tz->ops->get_trip_hyst) {
|
|
|
|
tz->trip_hyst_attrs = kzalloc(size, GFP_KERNEL);
|
|
|
|
if (!tz->trip_hyst_attrs) {
|
|
|
|
kfree(tz->trip_type_attrs);
|
|
|
|
kfree(tz->trip_temp_attrs);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
attrs = kzalloc(sizeof(*attrs) * (tz->trips * 3 + 1), GFP_KERNEL);
|
|
|
|
if (!attrs) {
|
|
|
|
kfree(tz->trip_type_attrs);
|
|
|
|
kfree(tz->trip_temp_attrs);
|
|
|
|
if (tz->ops->get_trip_hyst)
|
|
|
|
kfree(tz->trip_hyst_attrs);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:50 +03:00
|
|
|
for (indx = 0; indx < tz->trips; indx++) {
|
|
|
|
/* create trip type attribute */
|
|
|
|
snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
|
|
|
|
"trip_point_%d_type", indx);
|
|
|
|
|
|
|
|
sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);
|
|
|
|
tz->trip_type_attrs[indx].attr.attr.name =
|
|
|
|
tz->trip_type_attrs[indx].name;
|
|
|
|
tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO;
|
|
|
|
tz->trip_type_attrs[indx].attr.show = trip_point_type_show;
|
2016-11-08 08:08:52 +03:00
|
|
|
attrs[indx] = &tz->trip_type_attrs[indx].attr.attr;
|
2016-11-08 08:08:50 +03:00
|
|
|
|
|
|
|
/* create trip temp attribute */
|
|
|
|
snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
|
|
|
|
"trip_point_%d_temp", indx);
|
|
|
|
|
|
|
|
sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
|
|
|
|
tz->trip_temp_attrs[indx].attr.attr.name =
|
|
|
|
tz->trip_temp_attrs[indx].name;
|
|
|
|
tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
|
|
|
|
tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
|
|
|
|
if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
|
|
|
|
mask & (1 << indx)) {
|
|
|
|
tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
|
|
|
|
tz->trip_temp_attrs[indx].attr.store =
|
|
|
|
trip_point_temp_store;
|
|
|
|
}
|
2016-11-08 08:08:52 +03:00
|
|
|
attrs[indx + tz->trips] = &tz->trip_temp_attrs[indx].attr.attr;
|
2016-11-08 08:08:50 +03:00
|
|
|
|
|
|
|
/* create Optional trip hyst attribute */
|
|
|
|
if (!tz->ops->get_trip_hyst)
|
|
|
|
continue;
|
|
|
|
snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,
|
|
|
|
"trip_point_%d_hyst", indx);
|
|
|
|
|
|
|
|
sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);
|
|
|
|
tz->trip_hyst_attrs[indx].attr.attr.name =
|
|
|
|
tz->trip_hyst_attrs[indx].name;
|
|
|
|
tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;
|
|
|
|
tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;
|
|
|
|
if (tz->ops->set_trip_hyst) {
|
|
|
|
tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;
|
|
|
|
tz->trip_hyst_attrs[indx].attr.store =
|
|
|
|
trip_point_hyst_store;
|
|
|
|
}
|
2016-11-08 08:08:52 +03:00
|
|
|
attrs[indx + tz->trips * 2] =
|
|
|
|
&tz->trip_hyst_attrs[indx].attr.attr;
|
2016-11-08 08:08:50 +03:00
|
|
|
}
|
2016-11-08 08:08:52 +03:00
|
|
|
attrs[tz->trips * 3] = NULL;
|
2016-11-08 08:08:50 +03:00
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
tz->trips_attribute_group.attrs = attrs;
|
2016-11-08 08:08:50 +03:00
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
return 0;
|
2016-11-08 08:08:50 +03:00
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
static int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
|
|
|
|
int mask)
|
2016-11-08 08:08:51 +03:00
|
|
|
{
|
|
|
|
const struct attribute_group **groups;
|
2016-11-08 08:08:52 +03:00
|
|
|
int i, size, result;
|
2016-11-08 08:08:51 +03:00
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
/* we need one extra for trips and the NULL to terminate the array */
|
|
|
|
size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2;
|
2016-11-08 08:08:51 +03:00
|
|
|
/* This also takes care of API requirement to be NULL terminated */
|
|
|
|
groups = kcalloc(size, sizeof(*groups), GFP_KERNEL);
|
|
|
|
if (!groups)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
for (i = 0; i < size - 2; i++)
|
2016-11-08 08:08:51 +03:00
|
|
|
groups[i] = thermal_zone_attribute_groups[i];
|
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
if (tz->trips) {
|
|
|
|
result = create_trip_attrs(tz, mask);
|
|
|
|
if (result) {
|
|
|
|
kfree(groups);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
groups[size - 2] = &tz->trips_attribute_group;
|
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:51 +03:00
|
|
|
tz->device.groups = groups;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
/* sys I/F for cooling device */
|
|
|
|
#define to_cooling_device(_dev) \
|
|
|
|
container_of(_dev, struct thermal_cooling_device, device)
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_type_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", cdev->type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_max_state_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
unsigned long state;
|
|
|
|
int ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
ret = cdev->ops->get_max_state(cdev, &state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
return sprintf(buf, "%ld\n", state);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_cur_state_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
unsigned long state;
|
|
|
|
int ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
ret = cdev->ops->get_cur_state(cdev, &state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
return sprintf(buf, "%ld\n", state);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_cur_state_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
2008-11-27 20:48:13 +03:00
|
|
|
unsigned long state;
|
2008-01-17 10:51:08 +03:00
|
|
|
int result;
|
|
|
|
|
2008-11-27 20:48:13 +03:00
|
|
|
if (!sscanf(buf, "%ld\n", &state))
|
2008-01-17 10:51:08 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
2009-12-16 00:46:50 +03:00
|
|
|
if ((long)state < 0)
|
2008-01-17 10:51:08 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
result = cdev->ops->set_cur_state(cdev, state);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_cdev_type =
|
2008-02-08 00:55:08 +03:00
|
|
|
__ATTR(type, 0444, thermal_cooling_device_type_show, NULL);
|
2008-01-17 10:51:08 +03:00
|
|
|
static DEVICE_ATTR(max_state, 0444,
|
|
|
|
thermal_cooling_device_max_state_show, NULL);
|
|
|
|
static DEVICE_ATTR(cur_state, 0644,
|
|
|
|
thermal_cooling_device_cur_state_show,
|
|
|
|
thermal_cooling_device_cur_state_store);
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_trip_point_show(struct device *dev,
|
2008-02-08 00:55:08 +03:00
|
|
|
struct device_attribute *attr, char *buf)
|
2008-01-17 10:51:08 +03:00
|
|
|
{
|
2012-06-27 06:08:19 +04:00
|
|
|
struct thermal_instance *instance;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
instance =
|
2012-06-27 06:08:19 +04:00
|
|
|
container_of(attr, struct thermal_instance, attr);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (instance->trip == THERMAL_TRIPS_NONE)
|
|
|
|
return sprintf(buf, "-1\n");
|
|
|
|
else
|
|
|
|
return sprintf(buf, "%d\n", instance->trip);
|
|
|
|
}
|
|
|
|
|
2015-02-21 05:10:08 +03:00
|
|
|
static struct attribute *cooling_device_attrs[] = {
|
|
|
|
&dev_attr_cdev_type.attr,
|
|
|
|
&dev_attr_max_state.attr,
|
|
|
|
&dev_attr_cur_state.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group cooling_device_attr_group = {
|
|
|
|
.attrs = cooling_device_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group *cooling_device_attr_groups[] = {
|
|
|
|
&cooling_device_attr_group,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2015-02-18 19:04:24 +03:00
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_weight_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct thermal_instance *instance;
|
|
|
|
|
|
|
|
instance = container_of(attr, struct thermal_instance, weight_attr);
|
|
|
|
|
|
|
|
return sprintf(buf, "%d\n", instance->weight);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
thermal_cooling_device_weight_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct thermal_instance *instance;
|
|
|
|
int ret, weight;
|
|
|
|
|
|
|
|
ret = kstrtoint(buf, 0, &weight);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
instance = container_of(attr, struct thermal_instance, weight_attr);
|
|
|
|
instance->weight = weight;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
2008-01-17 10:51:08 +03:00
|
|
|
/* Device management */
|
|
|
|
|
|
|
|
/**
|
2013-04-24 01:48:16 +04:00
|
|
|
* thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
|
|
|
|
* @tz: pointer to struct thermal_zone_device
|
2008-01-17 10:51:08 +03:00
|
|
|
* @trip: indicates which trip point the cooling devices is
|
|
|
|
* associated with in this thermal zone.
|
2013-04-24 01:48:16 +04:00
|
|
|
* @cdev: pointer to struct thermal_cooling_device
|
|
|
|
* @upper: the Maximum cooling state for this trip point.
|
|
|
|
* THERMAL_NO_LIMIT means no upper limit,
|
|
|
|
* and the cooling device can be in max_state.
|
|
|
|
* @lower: the Minimum cooling state can be used for this trip point.
|
|
|
|
* THERMAL_NO_LIMIT means no lower limit,
|
|
|
|
* and the cooling device can be in cooling state 0.
|
2015-02-18 19:04:21 +03:00
|
|
|
* @weight: The weight of the cooling device to be bound to the
|
|
|
|
* thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
|
|
|
|
* default value
|
2008-02-08 00:55:08 +03:00
|
|
|
*
|
2013-04-24 01:48:16 +04:00
|
|
|
* This interface function bind a thermal cooling device to the certain trip
|
|
|
|
* point of a thermal zone device.
|
2008-02-08 00:55:08 +03:00
|
|
|
* This function is usually called in the thermal zone device .bind callback.
|
2013-04-24 01:48:16 +04:00
|
|
|
*
|
|
|
|
* Return: 0 on success, the proper error value otherwise.
|
2008-01-17 10:51:08 +03:00
|
|
|
*/
|
|
|
|
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
|
|
|
|
int trip,
|
2012-06-26 12:35:57 +04:00
|
|
|
struct thermal_cooling_device *cdev,
|
2015-02-18 19:04:21 +03:00
|
|
|
unsigned long upper, unsigned long lower,
|
|
|
|
unsigned int weight)
|
2008-01-17 10:51:08 +03:00
|
|
|
{
|
2012-06-27 06:08:19 +04:00
|
|
|
struct thermal_instance *dev;
|
|
|
|
struct thermal_instance *pos;
|
2008-02-15 08:58:50 +03:00
|
|
|
struct thermal_zone_device *pos1;
|
|
|
|
struct thermal_cooling_device *pos2;
|
2012-06-26 12:27:22 +04:00
|
|
|
unsigned long max_state;
|
2014-11-18 13:16:30 +03:00
|
|
|
int result, ret;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2008-02-08 00:55:08 +03:00
|
|
|
if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
|
2008-01-17 10:51:08 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-02-15 08:58:50 +03:00
|
|
|
list_for_each_entry(pos1, &thermal_tz_list, node) {
|
|
|
|
if (pos1 == tz)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list_for_each_entry(pos2, &thermal_cdev_list, node) {
|
|
|
|
if (pos2 == cdev)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tz != pos1 || cdev != pos2)
|
2008-01-17 10:51:08 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
2014-11-18 13:16:30 +03:00
|
|
|
ret = cdev->ops->get_max_state(cdev, &max_state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2012-06-26 12:35:57 +04:00
|
|
|
|
|
|
|
/* lower default 0, upper default max_state */
|
|
|
|
lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
|
|
|
|
upper = upper == THERMAL_NO_LIMIT ? max_state : upper;
|
|
|
|
|
|
|
|
if (lower > upper || upper > max_state)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
dev =
|
2012-06-27 06:08:19 +04:00
|
|
|
kzalloc(sizeof(struct thermal_instance), GFP_KERNEL);
|
2008-01-17 10:51:08 +03:00
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
dev->tz = tz;
|
|
|
|
dev->cdev = cdev;
|
|
|
|
dev->trip = trip;
|
2012-06-26 12:35:57 +04:00
|
|
|
dev->upper = upper;
|
|
|
|
dev->lower = lower;
|
2012-06-27 10:13:04 +04:00
|
|
|
dev->target = THERMAL_NO_TARGET;
|
2015-02-18 19:04:21 +03:00
|
|
|
dev->weight = weight;
|
2012-06-26 12:27:22 +04:00
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
result = get_idr(&tz->idr, &tz->lock, &dev->id);
|
|
|
|
if (result)
|
|
|
|
goto free_mem;
|
|
|
|
|
|
|
|
sprintf(dev->name, "cdev%d", dev->id);
|
|
|
|
result =
|
|
|
|
sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
|
|
|
|
if (result)
|
|
|
|
goto release_idr;
|
|
|
|
|
|
|
|
sprintf(dev->attr_name, "cdev%d_trip_point", dev->id);
|
2010-04-07 01:34:51 +04:00
|
|
|
sysfs_attr_init(&dev->attr.attr);
|
2008-01-17 10:51:08 +03:00
|
|
|
dev->attr.attr.name = dev->attr_name;
|
|
|
|
dev->attr.attr.mode = 0444;
|
|
|
|
dev->attr.show = thermal_cooling_device_trip_point_show;
|
|
|
|
result = device_create_file(&tz->device, &dev->attr);
|
|
|
|
if (result)
|
|
|
|
goto remove_symbol_link;
|
|
|
|
|
2015-02-18 19:04:24 +03:00
|
|
|
sprintf(dev->weight_attr_name, "cdev%d_weight", dev->id);
|
|
|
|
sysfs_attr_init(&dev->weight_attr.attr);
|
|
|
|
dev->weight_attr.attr.name = dev->weight_attr_name;
|
|
|
|
dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
|
|
|
|
dev->weight_attr.show = thermal_cooling_device_weight_show;
|
|
|
|
dev->weight_attr.store = thermal_cooling_device_weight_store;
|
|
|
|
result = device_create_file(&tz->device, &dev->weight_attr);
|
|
|
|
if (result)
|
|
|
|
goto remove_trip_file;
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_lock(&tz->lock);
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_lock(&cdev->lock);
|
2012-06-27 06:09:36 +04:00
|
|
|
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
|
2008-01-17 10:51:08 +03:00
|
|
|
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
|
|
|
|
result = -EEXIST;
|
|
|
|
break;
|
|
|
|
}
|
2012-06-27 10:11:52 +04:00
|
|
|
if (!result) {
|
2012-06-27 06:09:36 +04:00
|
|
|
list_add_tail(&dev->tz_node, &tz->thermal_instances);
|
2012-06-27 10:11:52 +04:00
|
|
|
list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
|
2015-10-30 11:32:10 +03:00
|
|
|
atomic_set(&tz->need_update, 1);
|
2012-06-27 10:11:52 +04:00
|
|
|
}
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
return 0;
|
|
|
|
|
2015-02-18 19:04:24 +03:00
|
|
|
device_remove_file(&tz->device, &dev->weight_attr);
|
|
|
|
remove_trip_file:
|
2008-01-17 10:51:08 +03:00
|
|
|
device_remove_file(&tz->device, &dev->attr);
|
2012-03-21 23:55:02 +04:00
|
|
|
remove_symbol_link:
|
2008-01-17 10:51:08 +03:00
|
|
|
sysfs_remove_link(&tz->device.kobj, dev->name);
|
2012-03-21 23:55:02 +04:00
|
|
|
release_idr:
|
2008-01-17 10:51:08 +03:00
|
|
|
release_idr(&tz->idr, &tz->lock, dev->id);
|
2012-03-21 23:55:02 +04:00
|
|
|
free_mem:
|
2008-01-17 10:51:08 +03:00
|
|
|
kfree(dev);
|
|
|
|
return result;
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
/**
|
2013-04-24 01:48:17 +04:00
|
|
|
* thermal_zone_unbind_cooling_device() - unbind a cooling device from a
|
|
|
|
* thermal zone.
|
|
|
|
* @tz: pointer to a struct thermal_zone_device.
|
2008-01-17 10:51:08 +03:00
|
|
|
* @trip: indicates which trip point the cooling devices is
|
|
|
|
* associated with in this thermal zone.
|
2013-04-24 01:48:17 +04:00
|
|
|
* @cdev: pointer to a struct thermal_cooling_device.
|
2008-02-08 00:55:08 +03:00
|
|
|
*
|
2013-04-24 01:48:17 +04:00
|
|
|
* This interface function unbind a thermal cooling device from the certain
|
|
|
|
* trip point of a thermal zone device.
|
2008-02-08 00:55:08 +03:00
|
|
|
* This function is usually called in the thermal zone device .unbind callback.
|
2013-04-24 01:48:17 +04:00
|
|
|
*
|
|
|
|
* Return: 0 on success, the proper error value otherwise.
|
2008-01-17 10:51:08 +03:00
|
|
|
*/
|
|
|
|
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
|
|
|
|
int trip,
|
|
|
|
struct thermal_cooling_device *cdev)
|
|
|
|
{
|
2012-06-27 06:08:19 +04:00
|
|
|
struct thermal_instance *pos, *next;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_lock(&cdev->lock);
|
2012-06-27 06:09:36 +04:00
|
|
|
list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
|
2008-02-08 00:55:08 +03:00
|
|
|
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
|
2012-06-27 06:09:36 +04:00
|
|
|
list_del(&pos->tz_node);
|
2012-06-27 10:11:52 +04:00
|
|
|
list_del(&pos->cdev_node);
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
goto unbind;
|
|
|
|
}
|
|
|
|
}
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
2012-03-21 23:55:02 +04:00
|
|
|
unbind:
|
2015-07-23 12:02:32 +03:00
|
|
|
device_remove_file(&tz->device, &pos->weight_attr);
|
2008-01-17 10:51:08 +03:00
|
|
|
device_remove_file(&tz->device, &pos->attr);
|
|
|
|
sysfs_remove_link(&tz->device.kobj, pos->name);
|
|
|
|
release_idr(&tz->idr, &tz->lock, pos->id);
|
|
|
|
kfree(pos);
|
|
|
|
return 0;
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
static void thermal_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
struct thermal_cooling_device *cdev;
|
|
|
|
|
2012-03-21 23:55:02 +04:00
|
|
|
if (!strncmp(dev_name(dev), "thermal_zone",
|
|
|
|
sizeof("thermal_zone") - 1)) {
|
2008-01-17 10:51:08 +03:00
|
|
|
tz = to_thermal_zone(dev);
|
|
|
|
kfree(tz);
|
2013-10-01 22:38:00 +04:00
|
|
|
} else if(!strncmp(dev_name(dev), "cooling_device",
|
|
|
|
sizeof("cooling_device") - 1)){
|
2008-01-17 10:51:08 +03:00
|
|
|
cdev = to_cooling_device(dev);
|
|
|
|
kfree(cdev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct class thermal_class = {
|
|
|
|
.name = "thermal",
|
|
|
|
.dev_release = thermal_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-09-26 23:55:01 +04:00
|
|
|
* __thermal_cooling_device_register() - register a new thermal cooling device
|
|
|
|
* @np: a pointer to a device tree node.
|
2008-01-17 10:51:08 +03:00
|
|
|
* @type: the thermal cooling device type.
|
|
|
|
* @devdata: device private data.
|
|
|
|
* @ops: standard thermal cooling devices callbacks.
|
2013-04-24 01:48:18 +04:00
|
|
|
*
|
|
|
|
* This interface function adds a new thermal cooling device (fan/processor/...)
|
|
|
|
* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
|
|
|
|
* to all the thermal zone devices registered at the same time.
|
2013-09-26 23:55:01 +04:00
|
|
|
* It also gives the opportunity to link the cooling device to a device tree
|
|
|
|
* node, so that it can be bound to a thermal zone created out of device tree.
|
2013-04-24 01:48:18 +04:00
|
|
|
*
|
|
|
|
* Return: a pointer to the created struct thermal_cooling_device or an
|
|
|
|
* ERR_PTR. Caller must check return value with IS_ERR*() helpers.
|
2008-01-17 10:51:08 +03:00
|
|
|
*/
|
2013-09-26 23:55:01 +04:00
|
|
|
static struct thermal_cooling_device *
|
|
|
|
__thermal_cooling_device_register(struct device_node *np,
|
|
|
|
char *type, void *devdata,
|
|
|
|
const struct thermal_cooling_device_ops *ops)
|
2008-01-17 10:51:08 +03:00
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev;
|
2015-10-30 11:32:10 +03:00
|
|
|
struct thermal_zone_device *pos = NULL;
|
2008-01-17 10:51:08 +03:00
|
|
|
int result;
|
|
|
|
|
2012-08-08 09:36:45 +04:00
|
|
|
if (type && strlen(type) >= THERMAL_NAME_LENGTH)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
|
2008-02-08 00:55:08 +03:00
|
|
|
!ops->set_cur_state)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL);
|
|
|
|
if (!cdev)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id);
|
|
|
|
if (result) {
|
|
|
|
kfree(cdev);
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(result);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2013-04-24 01:48:12 +04:00
|
|
|
strlcpy(cdev->type, type ? : "", sizeof(cdev->type));
|
2012-07-24 12:56:21 +04:00
|
|
|
mutex_init(&cdev->lock);
|
2012-06-27 10:11:52 +04:00
|
|
|
INIT_LIST_HEAD(&cdev->thermal_instances);
|
2013-09-26 23:55:01 +04:00
|
|
|
cdev->np = np;
|
2008-01-17 10:51:08 +03:00
|
|
|
cdev->ops = ops;
|
2014-02-17 07:02:55 +04:00
|
|
|
cdev->updated = false;
|
2008-01-17 10:51:08 +03:00
|
|
|
cdev->device.class = &thermal_class;
|
2015-02-21 05:10:08 +03:00
|
|
|
cdev->device.groups = cooling_device_attr_groups;
|
2008-01-17 10:51:08 +03:00
|
|
|
cdev->devdata = devdata;
|
2009-01-06 21:44:37 +03:00
|
|
|
dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
|
2008-01-17 10:51:08 +03:00
|
|
|
result = device_register(&cdev->device);
|
|
|
|
if (result) {
|
|
|
|
release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
|
|
|
|
kfree(cdev);
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(result);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
/* Add 'this' new cdev to the global cdev list */
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_add(&cdev->node, &thermal_cdev_list);
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
/* Update binding information for 'this' new cdev */
|
|
|
|
bind_cdev(cdev);
|
|
|
|
|
2015-10-30 11:32:10 +03:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
|
|
|
if (atomic_cmpxchg(&pos->need_update, 1, 0))
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(pos,
|
|
|
|
THERMAL_EVENT_UNSPECIFIED);
|
2015-10-30 11:32:10 +03:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
return cdev;
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
2013-09-26 23:55:01 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* thermal_cooling_device_register() - register a new thermal cooling device
|
|
|
|
* @type: the thermal cooling device type.
|
|
|
|
* @devdata: device private data.
|
|
|
|
* @ops: standard thermal cooling devices callbacks.
|
|
|
|
*
|
|
|
|
* This interface function adds a new thermal cooling device (fan/processor/...)
|
|
|
|
* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
|
|
|
|
* to all the thermal zone devices registered at the same time.
|
|
|
|
*
|
|
|
|
* Return: a pointer to the created struct thermal_cooling_device or an
|
|
|
|
* ERR_PTR. Caller must check return value with IS_ERR*() helpers.
|
|
|
|
*/
|
|
|
|
struct thermal_cooling_device *
|
|
|
|
thermal_cooling_device_register(char *type, void *devdata,
|
|
|
|
const struct thermal_cooling_device_ops *ops)
|
|
|
|
{
|
|
|
|
return __thermal_cooling_device_register(NULL, type, devdata, ops);
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2013-09-26 23:55:01 +04:00
|
|
|
/**
|
|
|
|
* thermal_of_cooling_device_register() - register an OF thermal cooling device
|
|
|
|
* @np: a pointer to a device tree node.
|
|
|
|
* @type: the thermal cooling device type.
|
|
|
|
* @devdata: device private data.
|
|
|
|
* @ops: standard thermal cooling devices callbacks.
|
|
|
|
*
|
|
|
|
* This function will register a cooling device with device tree node reference.
|
|
|
|
* This interface function adds a new thermal cooling device (fan/processor/...)
|
|
|
|
* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
|
|
|
|
* to all the thermal zone devices registered at the same time.
|
|
|
|
*
|
|
|
|
* Return: a pointer to the created struct thermal_cooling_device or an
|
|
|
|
* ERR_PTR. Caller must check return value with IS_ERR*() helpers.
|
|
|
|
*/
|
|
|
|
struct thermal_cooling_device *
|
|
|
|
thermal_of_cooling_device_register(struct device_node *np,
|
|
|
|
char *type, void *devdata,
|
|
|
|
const struct thermal_cooling_device_ops *ops)
|
|
|
|
{
|
|
|
|
return __thermal_cooling_device_register(np, type, devdata, ops);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
/**
|
|
|
|
* thermal_cooling_device_unregister - removes the registered thermal cooling device
|
|
|
|
* @cdev: the thermal cooling device to remove.
|
|
|
|
*
|
|
|
|
* thermal_cooling_device_unregister() must be called when the device is no
|
|
|
|
* longer needed.
|
|
|
|
*/
|
2012-09-18 09:34:59 +04:00
|
|
|
void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
|
2008-01-17 10:51:08 +03:00
|
|
|
{
|
2012-09-18 09:34:59 +04:00
|
|
|
int i;
|
|
|
|
const struct thermal_zone_params *tzp;
|
2008-01-17 10:51:08 +03:00
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
struct thermal_cooling_device *pos = NULL;
|
|
|
|
|
|
|
|
if (!cdev)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(pos, &thermal_cdev_list, node)
|
|
|
|
if (pos == cdev)
|
|
|
|
break;
|
|
|
|
if (pos != cdev) {
|
|
|
|
/* thermal cooling device not found */
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
list_del(&cdev->node);
|
2012-09-18 09:34:59 +04:00
|
|
|
|
|
|
|
/* Unbind all thermal zones associated with 'this' cdev */
|
2008-01-17 10:51:08 +03:00
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
2012-09-18 09:34:59 +04:00
|
|
|
if (tz->ops->unbind) {
|
|
|
|
tz->ops->unbind(tz, cdev);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tz->tzp || !tz->tzp->tbp)
|
2008-01-17 10:51:08 +03:00
|
|
|
continue;
|
2012-09-18 09:34:59 +04:00
|
|
|
|
|
|
|
tzp = tz->tzp;
|
|
|
|
for (i = 0; i < tzp->num_tbps; i++) {
|
|
|
|
if (tzp->tbp[i].cdev == cdev) {
|
|
|
|
__unbind(tz, tzp->tbp[i].trip_mask, cdev);
|
|
|
|
tzp->tbp[i].cdev = NULL;
|
|
|
|
}
|
|
|
|
}
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
2012-09-18 09:34:59 +04:00
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
2012-09-18 09:34:59 +04:00
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
|
|
|
|
device_unregister(&cdev->device);
|
|
|
|
return;
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2012-09-18 09:35:00 +04:00
|
|
|
void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
2012-06-27 10:13:04 +04:00
|
|
|
{
|
|
|
|
struct thermal_instance *instance;
|
|
|
|
unsigned long target = 0;
|
|
|
|
|
thermal: fix race condition when updating cooling device
When multiple thermal zones are bound to the same cooling device, multiple
kernel threads may want to update the cooling device state by calling
thermal_cdev_update(). Having cdev not protected by a mutex can lead to a race
condition. Consider the following situation with two kernel threads k1 and k2:
Thread k1 Thread k2
||
|| call thermal_cdev_update()
|| ...
|| set_cur_state(cdev, target);
call power_actor_set_power() ||
... ||
instance->target = state; ||
cdev->updated = false; ||
|| cdev->updated = true;
|| // completes execution
call thermal_cdev_update() ||
// cdev->updated == true ||
return; ||
\/
time
k2 has already looped through the thermal instances looking for the deepest
cooling device state and is preempted right before setting cdev->updated to
true. Now, k1 runs, modifies the thermal instance state and sets cdev->updated
to false. Then, k1 is preempted and k2 continues the execution by setting
cdev->updated to true, therefore preventing k1 from performing the update.
Notice that this is not an issue if k2 looks at the instance->target modified by
k1 "after" it is assigned by k1. In fact, in this case the update will happen
anyway and k1 can safely return immediately from thermal_cdev_update().
This may lead to a situation where a thermal governor never updates the cooling
device. For example, this is the case for the step_wise governor: when calling
the function thermal_zone_trip_update(), the governor may always get a new state
equal to the old one (which, however, wasn't notified to the cooling device) and
will therefore skip the update.
CC: Zhang Rui <rui.zhang@intel.com>
CC: Eduardo Valentin <edubezval@gmail.com>
CC: Peter Feuerer <peter@piie.net>
Reported-by: Toby Huang <toby.huang@arm.com>
Signed-off-by: Michele Di Giorgio <michele.digiorgio@arm.com>
Reviewed-by: Javi Merino <javi.merino@arm.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2016-06-02 17:25:31 +03:00
|
|
|
mutex_lock(&cdev->lock);
|
2012-06-27 10:13:04 +04:00
|
|
|
/* cooling device is updated*/
|
thermal: fix race condition when updating cooling device
When multiple thermal zones are bound to the same cooling device, multiple
kernel threads may want to update the cooling device state by calling
thermal_cdev_update(). Having cdev not protected by a mutex can lead to a race
condition. Consider the following situation with two kernel threads k1 and k2:
Thread k1 Thread k2
||
|| call thermal_cdev_update()
|| ...
|| set_cur_state(cdev, target);
call power_actor_set_power() ||
... ||
instance->target = state; ||
cdev->updated = false; ||
|| cdev->updated = true;
|| // completes execution
call thermal_cdev_update() ||
// cdev->updated == true ||
return; ||
\/
time
k2 has already looped through the thermal instances looking for the deepest
cooling device state and is preempted right before setting cdev->updated to
true. Now, k1 runs, modifies the thermal instance state and sets cdev->updated
to false. Then, k1 is preempted and k2 continues the execution by setting
cdev->updated to true, therefore preventing k1 from performing the update.
Notice that this is not an issue if k2 looks at the instance->target modified by
k1 "after" it is assigned by k1. In fact, in this case the update will happen
anyway and k1 can safely return immediately from thermal_cdev_update().
This may lead to a situation where a thermal governor never updates the cooling
device. For example, this is the case for the step_wise governor: when calling
the function thermal_zone_trip_update(), the governor may always get a new state
equal to the old one (which, however, wasn't notified to the cooling device) and
will therefore skip the update.
CC: Zhang Rui <rui.zhang@intel.com>
CC: Eduardo Valentin <edubezval@gmail.com>
CC: Peter Feuerer <peter@piie.net>
Reported-by: Toby Huang <toby.huang@arm.com>
Signed-off-by: Michele Di Giorgio <michele.digiorgio@arm.com>
Reviewed-by: Javi Merino <javi.merino@arm.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2016-06-02 17:25:31 +03:00
|
|
|
if (cdev->updated) {
|
|
|
|
mutex_unlock(&cdev->lock);
|
2012-06-27 10:13:04 +04:00
|
|
|
return;
|
thermal: fix race condition when updating cooling device
When multiple thermal zones are bound to the same cooling device, multiple
kernel threads may want to update the cooling device state by calling
thermal_cdev_update(). Having cdev not protected by a mutex can lead to a race
condition. Consider the following situation with two kernel threads k1 and k2:
Thread k1 Thread k2
||
|| call thermal_cdev_update()
|| ...
|| set_cur_state(cdev, target);
call power_actor_set_power() ||
... ||
instance->target = state; ||
cdev->updated = false; ||
|| cdev->updated = true;
|| // completes execution
call thermal_cdev_update() ||
// cdev->updated == true ||
return; ||
\/
time
k2 has already looped through the thermal instances looking for the deepest
cooling device state and is preempted right before setting cdev->updated to
true. Now, k1 runs, modifies the thermal instance state and sets cdev->updated
to false. Then, k1 is preempted and k2 continues the execution by setting
cdev->updated to true, therefore preventing k1 from performing the update.
Notice that this is not an issue if k2 looks at the instance->target modified by
k1 "after" it is assigned by k1. In fact, in this case the update will happen
anyway and k1 can safely return immediately from thermal_cdev_update().
This may lead to a situation where a thermal governor never updates the cooling
device. For example, this is the case for the step_wise governor: when calling
the function thermal_zone_trip_update(), the governor may always get a new state
equal to the old one (which, however, wasn't notified to the cooling device) and
will therefore skip the update.
CC: Zhang Rui <rui.zhang@intel.com>
CC: Eduardo Valentin <edubezval@gmail.com>
CC: Peter Feuerer <peter@piie.net>
Reported-by: Toby Huang <toby.huang@arm.com>
Signed-off-by: Michele Di Giorgio <michele.digiorgio@arm.com>
Reviewed-by: Javi Merino <javi.merino@arm.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2016-06-02 17:25:31 +03:00
|
|
|
}
|
2012-06-27 10:13:04 +04:00
|
|
|
|
|
|
|
/* Make sure cdev enters the deepest cooling state */
|
|
|
|
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
|
thermal: debug: add debug statement for core and step_wise
To ease debugging thermal problem, add these dynamic debug statements
so that user do not need rebuild kernel to see these info.
Based on a patch from Zhang Rui for debugging on bugzilla:
https://bugzilla.kernel.org/attachment.cgi?id=98671
A sample output after we turn on dynamic debug with the following cmd:
# echo 'module thermal_sys +fp' > /sys/kernel/debug/dynamic_debug/control
is like:
[ 355.147627] update_temperature: thermal thermal_zone0: last_temperature=52000, current_temperature=55000
[ 355.147636] thermal_zone_trip_update: thermal thermal_zone0: Trip1[type=1,temp=79000]:trend=2,throttle=0
[ 355.147644] get_target_state: thermal cooling_device8: cur_state=0
[ 355.147647] thermal_zone_trip_update: thermal cooling_device8: old_target=-1, target=-1
[ 355.147652] get_target_state: thermal cooling_device7: cur_state=0
[ 355.147655] thermal_zone_trip_update: thermal cooling_device7: old_target=-1, target=-1
[ 355.147660] get_target_state: thermal cooling_device6: cur_state=0
[ 355.147663] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 355.147668] get_target_state: thermal cooling_device5: cur_state=0
[ 355.147671] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 355.147678] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=90000]:trend=1,throttle=0
[ 355.147776] get_target_state: thermal cooling_device0: cur_state=0
[ 355.147783] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[ 355.147792] thermal_zone_trip_update: thermal thermal_zone0: Trip3[type=0,temp=80000]:trend=1,throttle=0
[ 355.147845] get_target_state: thermal cooling_device1: cur_state=0
[ 355.147849] thermal_zone_trip_update: thermal cooling_device1: old_target=-1, target=-1
[ 355.147856] thermal_zone_trip_update: thermal thermal_zone0: Trip4[type=0,temp=70000]:trend=1,throttle=0
[ 355.147904] get_target_state: thermal cooling_device2: cur_state=0
[ 355.147908] thermal_zone_trip_update: thermal cooling_device2: old_target=-1, target=-1
[ 355.147915] thermal_zone_trip_update: thermal thermal_zone0: Trip5[type=0,temp=60000]:trend=1,throttle=0
[ 355.147963] get_target_state: thermal cooling_device3: cur_state=0
[ 355.147967] thermal_zone_trip_update: thermal cooling_device3: old_target=-1, target=-1
[ 355.147973] thermal_zone_trip_update: thermal thermal_zone0: Trip6[type=0,temp=55000]:trend=1,throttle=1
[ 355.148022] get_target_state: thermal cooling_device4: cur_state=0
[ 355.148025] thermal_zone_trip_update: thermal cooling_device4: old_target=-1, target=1
[ 355.148036] thermal_cdev_update: thermal cooling_device4: zone0->target=1
[ 355.169279] thermal_cdev_update: thermal cooling_device4: set to state 1
Signed-off-by: Aaron Lu <aaron.lu@intel.com>
Acked-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2013-12-02 09:54:26 +04:00
|
|
|
dev_dbg(&cdev->device, "zone%d->target=%lu\n",
|
|
|
|
instance->tz->id, instance->target);
|
2012-06-27 10:13:04 +04:00
|
|
|
if (instance->target == THERMAL_NO_TARGET)
|
|
|
|
continue;
|
|
|
|
if (instance->target > target)
|
|
|
|
target = instance->target;
|
|
|
|
}
|
|
|
|
cdev->ops->set_cur_state(cdev, target);
|
|
|
|
cdev->updated = true;
|
thermal: fix race condition when updating cooling device
When multiple thermal zones are bound to the same cooling device, multiple
kernel threads may want to update the cooling device state by calling
thermal_cdev_update(). Having cdev not protected by a mutex can lead to a race
condition. Consider the following situation with two kernel threads k1 and k2:
Thread k1 Thread k2
||
|| call thermal_cdev_update()
|| ...
|| set_cur_state(cdev, target);
call power_actor_set_power() ||
... ||
instance->target = state; ||
cdev->updated = false; ||
|| cdev->updated = true;
|| // completes execution
call thermal_cdev_update() ||
// cdev->updated == true ||
return; ||
\/
time
k2 has already looped through the thermal instances looking for the deepest
cooling device state and is preempted right before setting cdev->updated to
true. Now, k1 runs, modifies the thermal instance state and sets cdev->updated
to false. Then, k1 is preempted and k2 continues the execution by setting
cdev->updated to true, therefore preventing k1 from performing the update.
Notice that this is not an issue if k2 looks at the instance->target modified by
k1 "after" it is assigned by k1. In fact, in this case the update will happen
anyway and k1 can safely return immediately from thermal_cdev_update().
This may lead to a situation where a thermal governor never updates the cooling
device. For example, this is the case for the step_wise governor: when calling
the function thermal_zone_trip_update(), the governor may always get a new state
equal to the old one (which, however, wasn't notified to the cooling device) and
will therefore skip the update.
CC: Zhang Rui <rui.zhang@intel.com>
CC: Eduardo Valentin <edubezval@gmail.com>
CC: Peter Feuerer <peter@piie.net>
Reported-by: Toby Huang <toby.huang@arm.com>
Signed-off-by: Michele Di Giorgio <michele.digiorgio@arm.com>
Reviewed-by: Javi Merino <javi.merino@arm.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2016-06-02 17:25:31 +03:00
|
|
|
mutex_unlock(&cdev->lock);
|
2014-07-29 14:50:49 +04:00
|
|
|
trace_cdev_update(cdev, target);
|
thermal: debug: add debug statement for core and step_wise
To ease debugging thermal problem, add these dynamic debug statements
so that user do not need rebuild kernel to see these info.
Based on a patch from Zhang Rui for debugging on bugzilla:
https://bugzilla.kernel.org/attachment.cgi?id=98671
A sample output after we turn on dynamic debug with the following cmd:
# echo 'module thermal_sys +fp' > /sys/kernel/debug/dynamic_debug/control
is like:
[ 355.147627] update_temperature: thermal thermal_zone0: last_temperature=52000, current_temperature=55000
[ 355.147636] thermal_zone_trip_update: thermal thermal_zone0: Trip1[type=1,temp=79000]:trend=2,throttle=0
[ 355.147644] get_target_state: thermal cooling_device8: cur_state=0
[ 355.147647] thermal_zone_trip_update: thermal cooling_device8: old_target=-1, target=-1
[ 355.147652] get_target_state: thermal cooling_device7: cur_state=0
[ 355.147655] thermal_zone_trip_update: thermal cooling_device7: old_target=-1, target=-1
[ 355.147660] get_target_state: thermal cooling_device6: cur_state=0
[ 355.147663] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 355.147668] get_target_state: thermal cooling_device5: cur_state=0
[ 355.147671] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 355.147678] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=90000]:trend=1,throttle=0
[ 355.147776] get_target_state: thermal cooling_device0: cur_state=0
[ 355.147783] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[ 355.147792] thermal_zone_trip_update: thermal thermal_zone0: Trip3[type=0,temp=80000]:trend=1,throttle=0
[ 355.147845] get_target_state: thermal cooling_device1: cur_state=0
[ 355.147849] thermal_zone_trip_update: thermal cooling_device1: old_target=-1, target=-1
[ 355.147856] thermal_zone_trip_update: thermal thermal_zone0: Trip4[type=0,temp=70000]:trend=1,throttle=0
[ 355.147904] get_target_state: thermal cooling_device2: cur_state=0
[ 355.147908] thermal_zone_trip_update: thermal cooling_device2: old_target=-1, target=-1
[ 355.147915] thermal_zone_trip_update: thermal thermal_zone0: Trip5[type=0,temp=60000]:trend=1,throttle=0
[ 355.147963] get_target_state: thermal cooling_device3: cur_state=0
[ 355.147967] thermal_zone_trip_update: thermal cooling_device3: old_target=-1, target=-1
[ 355.147973] thermal_zone_trip_update: thermal thermal_zone0: Trip6[type=0,temp=55000]:trend=1,throttle=1
[ 355.148022] get_target_state: thermal cooling_device4: cur_state=0
[ 355.148025] thermal_zone_trip_update: thermal cooling_device4: old_target=-1, target=1
[ 355.148036] thermal_cdev_update: thermal cooling_device4: zone0->target=1
[ 355.169279] thermal_cdev_update: thermal cooling_device4: set to state 1
Signed-off-by: Aaron Lu <aaron.lu@intel.com>
Acked-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2013-12-02 09:54:26 +04:00
|
|
|
dev_dbg(&cdev->device, "set to state %lu\n", target);
|
2012-06-27 10:13:04 +04:00
|
|
|
}
|
2012-09-18 09:35:00 +04:00
|
|
|
EXPORT_SYMBOL(thermal_cdev_update);
|
2012-06-27 10:13:04 +04:00
|
|
|
|
2012-09-18 09:35:05 +04:00
|
|
|
/**
|
2013-04-24 01:48:14 +04:00
|
|
|
* thermal_notify_framework - Sensor drivers use this API to notify framework
|
2012-09-18 09:35:05 +04:00
|
|
|
* @tz: thermal zone device
|
|
|
|
* @trip: indicates which trip point has been crossed
|
|
|
|
*
|
|
|
|
* This function handles the trip events from sensor drivers. It starts
|
|
|
|
* throttling the cooling devices according to the policy configured.
|
|
|
|
* For CRITICAL and HOT trip points, this notifies the respective drivers,
|
|
|
|
* and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
|
|
|
|
* The throttling policy is based on the configured platform data; if no
|
|
|
|
* platform data is provided, this uses the step_wise throttling policy.
|
|
|
|
*/
|
2013-04-24 01:48:14 +04:00
|
|
|
void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
|
2012-09-18 09:35:05 +04:00
|
|
|
{
|
|
|
|
handle_thermal_trip(tz, trip);
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_notify_framework);
|
2012-09-18 09:35:05 +04:00
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
/**
|
2013-04-24 01:48:20 +04:00
|
|
|
* thermal_zone_device_register() - register a new thermal zone device
|
2008-01-17 10:51:08 +03:00
|
|
|
* @type: the thermal zone device type
|
|
|
|
* @trips: the number of trip points the thermal zone support
|
2012-07-25 06:10:58 +04:00
|
|
|
* @mask: a bit string indicating the writeablility of trip points
|
2008-01-17 10:51:08 +03:00
|
|
|
* @devdata: private device data
|
|
|
|
* @ops: standard thermal zone device callbacks
|
2012-09-18 09:34:56 +04:00
|
|
|
* @tzp: thermal zone platform parameters
|
2008-12-03 20:55:32 +03:00
|
|
|
* @passive_delay: number of milliseconds to wait between polls when
|
|
|
|
* performing passive cooling
|
|
|
|
* @polling_delay: number of milliseconds to wait between polls when checking
|
|
|
|
* whether trip points have been crossed (0 for interrupt
|
|
|
|
* driven systems)
|
2008-01-17 10:51:08 +03:00
|
|
|
*
|
2013-04-24 01:48:20 +04:00
|
|
|
* This interface function adds a new thermal zone device (sensor) to
|
|
|
|
* /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the
|
|
|
|
* thermal cooling devices registered at the same time.
|
2008-01-17 10:51:08 +03:00
|
|
|
* thermal_zone_device_unregister() must be called when the device is no
|
2012-06-27 05:51:12 +04:00
|
|
|
* longer needed. The passive cooling depends on the .get_trend() return value.
|
2013-04-24 01:48:20 +04:00
|
|
|
*
|
|
|
|
* Return: a pointer to the created struct thermal_zone_device or an
|
|
|
|
* in case of error, an ERR_PTR. Caller must check return value with
|
|
|
|
* IS_ERR*() helpers.
|
2008-01-17 10:51:08 +03:00
|
|
|
*/
|
2012-07-31 15:39:30 +04:00
|
|
|
struct thermal_zone_device *thermal_zone_device_register(const char *type,
|
2012-07-25 06:10:58 +04:00
|
|
|
int trips, int mask, void *devdata,
|
2013-07-03 23:35:39 +04:00
|
|
|
struct thermal_zone_device_ops *ops,
|
2015-03-02 20:17:19 +03:00
|
|
|
struct thermal_zone_params *tzp,
|
2012-06-27 05:51:12 +04:00
|
|
|
int passive_delay, int polling_delay)
|
2008-01-17 10:51:08 +03:00
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
2008-12-03 21:00:38 +03:00
|
|
|
enum thermal_trip_type trip_type;
|
2016-03-18 05:03:24 +03:00
|
|
|
int trip_temp;
|
2008-01-17 10:51:08 +03:00
|
|
|
int result;
|
|
|
|
int count;
|
2015-02-26 22:00:27 +03:00
|
|
|
struct thermal_governor *governor;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2016-11-08 08:08:39 +03:00
|
|
|
if (!type || strlen(type) == 0)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2012-08-08 09:36:45 +04:00
|
|
|
if (type && strlen(type) >= THERMAL_NAME_LENGTH)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2012-07-25 06:10:58 +04:00
|
|
|
if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2013-09-13 03:15:44 +04:00
|
|
|
if (!ops)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2013-05-18 13:50:26 +04:00
|
|
|
if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
|
2013-01-02 19:29:42 +04:00
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL);
|
|
|
|
if (!tz)
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2012-06-27 06:09:00 +04:00
|
|
|
INIT_LIST_HEAD(&tz->thermal_instances);
|
2008-01-17 10:51:08 +03:00
|
|
|
idr_init(&tz->idr);
|
|
|
|
mutex_init(&tz->lock);
|
|
|
|
result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id);
|
|
|
|
if (result) {
|
|
|
|
kfree(tz);
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(result);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2016-11-08 08:08:39 +03:00
|
|
|
strlcpy(tz->type, type, sizeof(tz->type));
|
2008-01-17 10:51:08 +03:00
|
|
|
tz->ops = ops;
|
2012-09-18 09:34:56 +04:00
|
|
|
tz->tzp = tzp;
|
2008-01-17 10:51:08 +03:00
|
|
|
tz->device.class = &thermal_class;
|
|
|
|
tz->devdata = devdata;
|
|
|
|
tz->trips = trips;
|
2008-12-03 20:55:32 +03:00
|
|
|
tz->passive_delay = passive_delay;
|
|
|
|
tz->polling_delay = polling_delay;
|
2016-11-08 08:08:42 +03:00
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
/* sys I/F */
|
2016-11-08 08:08:42 +03:00
|
|
|
/* Add nodes that are always present via .groups */
|
2016-11-08 08:08:52 +03:00
|
|
|
result = thermal_zone_create_device_groups(tz, mask);
|
|
|
|
if (result)
|
|
|
|
goto unregister;
|
|
|
|
|
2015-10-30 11:32:10 +03:00
|
|
|
/* A new thermal zone needs to be updated anyway. */
|
|
|
|
atomic_set(&tz->need_update, 1);
|
2008-12-03 20:55:32 +03:00
|
|
|
|
2009-01-06 21:44:37 +03:00
|
|
|
dev_set_name(&tz->device, "thermal_zone%d", tz->id);
|
2008-01-17 10:51:08 +03:00
|
|
|
result = device_register(&tz->device);
|
|
|
|
if (result) {
|
|
|
|
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
|
|
|
|
kfree(tz);
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(result);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for (count = 0; count < trips; count++) {
|
2016-03-18 05:03:24 +03:00
|
|
|
if (tz->ops->get_trip_type(tz, count, &trip_type))
|
|
|
|
set_bit(count, &tz->trips_disabled);
|
|
|
|
if (tz->ops->get_trip_temp(tz, count, &trip_temp))
|
|
|
|
set_bit(count, &tz->trips_disabled);
|
|
|
|
/* Check for bogus trip points */
|
|
|
|
if (trip_temp == 0)
|
|
|
|
set_bit(count, &tz->trips_disabled);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2012-09-18 09:34:57 +04:00
|
|
|
/* Update 'this' zone's governor information */
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
if (tz->tzp)
|
2015-02-26 22:00:27 +03:00
|
|
|
governor = __find_governor(tz->tzp->governor_name);
|
2012-09-18 09:34:57 +04:00
|
|
|
else
|
2015-02-26 22:00:27 +03:00
|
|
|
governor = def_governor;
|
|
|
|
|
|
|
|
result = thermal_set_governor(tz, governor);
|
|
|
|
if (result) {
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
goto unregister;
|
|
|
|
}
|
2012-09-18 09:34:57 +04:00
|
|
|
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
|
2013-08-15 19:34:17 +04:00
|
|
|
if (!tz->tzp || !tz->tzp->no_hwmon) {
|
|
|
|
result = thermal_add_hwmon_sysfs(tz);
|
|
|
|
if (result)
|
|
|
|
goto unregister;
|
|
|
|
}
|
2008-04-21 12:07:52 +04:00
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_add_tail(&tz->node, &thermal_tz_list);
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
/* Bind cooling devices for this zone */
|
|
|
|
bind_tz(tz);
|
|
|
|
|
2008-12-03 20:55:32 +03:00
|
|
|
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
|
|
|
|
|
2015-10-30 11:31:47 +03:00
|
|
|
thermal_zone_device_reset(tz);
|
2015-10-30 11:32:10 +03:00
|
|
|
/* Update the new thermal zone and mark it as already updated. */
|
|
|
|
if (atomic_cmpxchg(&tz->need_update, 1, 0))
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2008-12-03 20:55:32 +03:00
|
|
|
|
2014-10-28 10:40:25 +03:00
|
|
|
return tz;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2012-03-21 23:55:02 +04:00
|
|
|
unregister:
|
2008-01-17 10:51:08 +03:00
|
|
|
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
|
|
|
|
device_unregister(&tz->device);
|
2008-02-15 08:59:50 +03:00
|
|
|
return ERR_PTR(result);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_register);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* thermal_device_unregister - removes the registered thermal zone device
|
|
|
|
* @tz: the thermal zone device to remove
|
|
|
|
*/
|
|
|
|
void thermal_zone_device_unregister(struct thermal_zone_device *tz)
|
|
|
|
{
|
2012-09-18 09:34:59 +04:00
|
|
|
int i;
|
|
|
|
const struct thermal_zone_params *tzp;
|
2008-01-17 10:51:08 +03:00
|
|
|
struct thermal_cooling_device *cdev;
|
|
|
|
struct thermal_zone_device *pos = NULL;
|
|
|
|
|
|
|
|
if (!tz)
|
|
|
|
return;
|
|
|
|
|
2012-09-18 09:34:59 +04:00
|
|
|
tzp = tz->tzp;
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
|
|
|
if (pos == tz)
|
|
|
|
break;
|
|
|
|
if (pos != tz) {
|
|
|
|
/* thermal zone device not found */
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
list_del(&tz->node);
|
2012-09-18 09:34:59 +04:00
|
|
|
|
|
|
|
/* Unbind all cdevs associated with 'this' thermal zone */
|
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node) {
|
|
|
|
if (tz->ops->unbind) {
|
|
|
|
tz->ops->unbind(tz, cdev);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tzp || !tzp->tbp)
|
|
|
|
break;
|
|
|
|
|
|
|
|
for (i = 0; i < tzp->num_tbps; i++) {
|
|
|
|
if (tzp->tbp[i].cdev == cdev) {
|
|
|
|
__unbind(tz, tzp->tbp[i].trip_mask, cdev);
|
|
|
|
tzp->tbp[i].cdev = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2008-12-03 20:55:32 +03:00
|
|
|
thermal_zone_device_set_polling(tz, 0);
|
|
|
|
|
2016-11-08 08:08:52 +03:00
|
|
|
kfree(tz->trip_type_attrs);
|
|
|
|
kfree(tz->trip_temp_attrs);
|
|
|
|
kfree(tz->trip_hyst_attrs);
|
|
|
|
kfree(tz->trips_attribute_group.attrs);
|
2015-02-26 22:00:27 +03:00
|
|
|
thermal_set_governor(tz, NULL);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2008-04-21 12:07:52 +04:00
|
|
|
thermal_remove_hwmon_sysfs(tz);
|
2008-01-17 10:51:08 +03:00
|
|
|
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
|
|
|
|
idr_destroy(&tz->idr);
|
|
|
|
mutex_destroy(&tz->lock);
|
|
|
|
device_unregister(&tz->device);
|
2016-11-08 08:08:51 +03:00
|
|
|
kfree(tz->device.groups);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
|
2008-01-17 10:51:08 +03:00
|
|
|
|
2013-04-05 16:32:28 +04:00
|
|
|
/**
|
|
|
|
* thermal_zone_get_zone_by_name() - search for a zone and returns its ref
|
|
|
|
* @name: thermal zone name to fetch the temperature
|
|
|
|
*
|
|
|
|
* When only one zone is found with the passed name, returns a reference to it.
|
|
|
|
*
|
|
|
|
* Return: On success returns a reference to an unique thermal zone with
|
|
|
|
* matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
|
|
|
|
* paramenters, -ENODEV for not found and -EEXIST for multiple matches).
|
|
|
|
*/
|
|
|
|
struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *pos = NULL, *ref = ERR_PTR(-EINVAL);
|
|
|
|
unsigned int found = 0;
|
|
|
|
|
|
|
|
if (!name)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
2014-10-14 02:55:01 +04:00
|
|
|
if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
|
2013-04-05 16:32:28 +04:00
|
|
|
found++;
|
|
|
|
ref = pos;
|
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
|
|
|
/* nothing has been found, thus an error code for it */
|
|
|
|
if (found == 0)
|
|
|
|
ref = ERR_PTR(-ENODEV);
|
|
|
|
else if (found > 1)
|
|
|
|
/* Success only when an unique zone is found */
|
|
|
|
ref = ERR_PTR(-EEXIST);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
|
|
|
|
|
2016-05-05 11:51:42 +03:00
|
|
|
/**
|
|
|
|
* thermal_zone_get_slope - return the slope attribute of the thermal zone
|
|
|
|
* @tz: thermal zone device with the slope attribute
|
|
|
|
*
|
|
|
|
* Return: If the thermal zone device has a slope attribute, return it, else
|
|
|
|
* return 1.
|
|
|
|
*/
|
|
|
|
int thermal_zone_get_slope(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
if (tz && tz->tzp)
|
|
|
|
return tz->tzp->slope;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* thermal_zone_get_offset - return the offset attribute of the thermal zone
|
|
|
|
* @tz: thermal zone device with the offset attribute
|
|
|
|
*
|
|
|
|
* Return: If the thermal zone device has a offset attribute, return it, else
|
|
|
|
* return 0.
|
|
|
|
*/
|
|
|
|
int thermal_zone_get_offset(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
if (tz && tz->tzp)
|
|
|
|
return tz->tzp->offset;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_offset);
|
|
|
|
|
2011-03-01 03:12:19 +03:00
|
|
|
#ifdef CONFIG_NET
|
2013-11-19 18:19:39 +04:00
|
|
|
static const struct genl_multicast_group thermal_event_mcgrps[] = {
|
|
|
|
{ .name = THERMAL_GENL_MCAST_GROUP_NAME, },
|
|
|
|
};
|
|
|
|
|
2011-03-01 03:12:19 +03:00
|
|
|
static struct genl_family thermal_event_genl_family = {
|
|
|
|
.id = GENL_ID_GENERATE,
|
|
|
|
.name = THERMAL_GENL_FAMILY_NAME,
|
|
|
|
.version = THERMAL_GENL_VERSION,
|
|
|
|
.maxattr = THERMAL_GENL_ATTR_MAX,
|
2013-11-19 18:19:39 +04:00
|
|
|
.mcgrps = thermal_event_mcgrps,
|
|
|
|
.n_mcgrps = ARRAY_SIZE(thermal_event_mcgrps),
|
2011-03-01 03:12:19 +03:00
|
|
|
};
|
|
|
|
|
2013-01-02 19:29:39 +04:00
|
|
|
int thermal_generate_netlink_event(struct thermal_zone_device *tz,
|
|
|
|
enum events event)
|
2010-10-27 02:03:29 +04:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct nlattr *attr;
|
|
|
|
struct thermal_genl_event *thermal_event;
|
|
|
|
void *msg_header;
|
|
|
|
int size;
|
|
|
|
int result;
|
2012-03-21 23:55:00 +04:00
|
|
|
static unsigned int thermal_event_seqnum;
|
2010-10-27 02:03:29 +04:00
|
|
|
|
2013-01-02 19:29:39 +04:00
|
|
|
if (!tz)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2010-10-27 02:03:29 +04:00
|
|
|
/* allocate memory */
|
2012-03-21 23:55:01 +04:00
|
|
|
size = nla_total_size(sizeof(struct thermal_genl_event)) +
|
|
|
|
nla_total_size(0);
|
2010-10-27 02:03:29 +04:00
|
|
|
|
|
|
|
skb = genlmsg_new(size, GFP_ATOMIC);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* add the genetlink message header */
|
|
|
|
msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++,
|
|
|
|
&thermal_event_genl_family, 0,
|
|
|
|
THERMAL_GENL_CMD_EVENT);
|
|
|
|
if (!msg_header) {
|
|
|
|
nlmsg_free(skb);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fill the data */
|
2012-03-21 23:55:01 +04:00
|
|
|
attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT,
|
|
|
|
sizeof(struct thermal_genl_event));
|
2010-10-27 02:03:29 +04:00
|
|
|
|
|
|
|
if (!attr) {
|
|
|
|
nlmsg_free(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
thermal_event = nla_data(attr);
|
|
|
|
if (!thermal_event) {
|
|
|
|
nlmsg_free(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(thermal_event, 0, sizeof(struct thermal_genl_event));
|
|
|
|
|
2013-01-02 19:29:39 +04:00
|
|
|
thermal_event->orig = tz->id;
|
2010-10-27 02:03:29 +04:00
|
|
|
thermal_event->event = event;
|
|
|
|
|
|
|
|
/* send multicast genetlink message */
|
2015-01-17 00:09:00 +03:00
|
|
|
genlmsg_end(skb, msg_header);
|
2010-10-27 02:03:29 +04:00
|
|
|
|
2013-11-19 18:19:38 +04:00
|
|
|
result = genlmsg_multicast(&thermal_event_genl_family, skb, 0,
|
2013-11-19 18:19:39 +04:00
|
|
|
0, GFP_ATOMIC);
|
2010-10-27 02:03:29 +04:00
|
|
|
if (result)
|
2013-01-02 19:29:41 +04:00
|
|
|
dev_err(&tz->device, "Failed to send netlink event:%d", result);
|
2010-10-27 02:03:29 +04:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2013-04-24 01:48:15 +04:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_generate_netlink_event);
|
2010-10-27 02:03:29 +04:00
|
|
|
|
|
|
|
static int genetlink_init(void)
|
|
|
|
{
|
2013-11-19 18:19:39 +04:00
|
|
|
return genl_register_family(&thermal_event_genl_family);
|
2010-10-27 02:03:29 +04:00
|
|
|
}
|
|
|
|
|
2011-03-01 03:12:19 +03:00
|
|
|
static void genetlink_exit(void)
|
|
|
|
{
|
|
|
|
genl_unregister_family(&thermal_event_genl_family);
|
|
|
|
}
|
|
|
|
#else /* !CONFIG_NET */
|
|
|
|
static inline int genetlink_init(void) { return 0; }
|
|
|
|
static inline void genetlink_exit(void) {}
|
|
|
|
#endif /* !CONFIG_NET */
|
|
|
|
|
2013-03-26 12:38:29 +04:00
|
|
|
static int __init thermal_register_governors(void)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
result = thermal_gov_step_wise_register();
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
result = thermal_gov_fair_share_register();
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
2014-07-22 19:37:13 +04:00
|
|
|
result = thermal_gov_bang_bang_register();
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
2015-03-02 20:17:19 +03:00
|
|
|
result = thermal_gov_user_space_register();
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
return thermal_gov_power_allocator_register();
|
2013-03-26 12:38:29 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void thermal_unregister_governors(void)
|
|
|
|
{
|
|
|
|
thermal_gov_step_wise_unregister();
|
|
|
|
thermal_gov_fair_share_unregister();
|
2014-07-22 19:37:13 +04:00
|
|
|
thermal_gov_bang_bang_unregister();
|
2013-03-26 12:38:29 +04:00
|
|
|
thermal_gov_user_space_unregister();
|
2015-03-02 20:17:19 +03:00
|
|
|
thermal_gov_power_allocator_unregister();
|
2013-03-26 12:38:29 +04:00
|
|
|
}
|
|
|
|
|
2015-10-30 11:31:58 +03:00
|
|
|
static int thermal_pm_notify(struct notifier_block *nb,
|
|
|
|
unsigned long mode, void *_unused)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case PM_HIBERNATION_PREPARE:
|
|
|
|
case PM_RESTORE_PREPARE:
|
|
|
|
case PM_SUSPEND_PREPARE:
|
|
|
|
atomic_set(&in_suspend, 1);
|
|
|
|
break;
|
|
|
|
case PM_POST_HIBERNATION:
|
|
|
|
case PM_POST_RESTORE:
|
|
|
|
case PM_POST_SUSPEND:
|
|
|
|
atomic_set(&in_suspend, 0);
|
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
|
|
|
thermal_zone_device_reset(tz);
|
2016-08-27 02:21:16 +03:00
|
|
|
thermal_zone_device_update(tz,
|
|
|
|
THERMAL_EVENT_UNSPECIFIED);
|
2015-10-30 11:31:58 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block thermal_pm_nb = {
|
|
|
|
.notifier_call = thermal_pm_notify,
|
|
|
|
};
|
|
|
|
|
2008-01-17 10:51:08 +03:00
|
|
|
static int __init thermal_init(void)
|
|
|
|
{
|
2013-03-26 12:38:29 +04:00
|
|
|
int result;
|
|
|
|
|
|
|
|
result = thermal_register_governors();
|
|
|
|
if (result)
|
|
|
|
goto error;
|
2008-01-17 10:51:08 +03:00
|
|
|
|
|
|
|
result = class_register(&thermal_class);
|
2013-03-26 12:38:29 +04:00
|
|
|
if (result)
|
|
|
|
goto unregister_governors;
|
|
|
|
|
2010-10-27 02:03:29 +04:00
|
|
|
result = genetlink_init();
|
2013-03-26 12:38:29 +04:00
|
|
|
if (result)
|
|
|
|
goto unregister_class;
|
|
|
|
|
2013-07-03 23:35:39 +04:00
|
|
|
result = of_parse_thermal_zones();
|
|
|
|
if (result)
|
|
|
|
goto exit_netlink;
|
|
|
|
|
2015-10-30 11:31:58 +03:00
|
|
|
result = register_pm_notifier(&thermal_pm_nb);
|
|
|
|
if (result)
|
|
|
|
pr_warn("Thermal: Can not register suspend notifier, return %d\n",
|
|
|
|
result);
|
|
|
|
|
2013-03-26 12:38:29 +04:00
|
|
|
return 0;
|
|
|
|
|
2013-07-03 23:35:39 +04:00
|
|
|
exit_netlink:
|
|
|
|
genetlink_exit();
|
2013-03-26 12:38:29 +04:00
|
|
|
unregister_class:
|
|
|
|
class_unregister(&thermal_class);
|
2014-12-04 00:20:21 +03:00
|
|
|
unregister_governors:
|
|
|
|
thermal_unregister_governors();
|
2013-03-26 12:38:29 +04:00
|
|
|
error:
|
|
|
|
idr_destroy(&thermal_tz_idr);
|
|
|
|
idr_destroy(&thermal_cdev_idr);
|
|
|
|
mutex_destroy(&thermal_idr_lock);
|
|
|
|
mutex_destroy(&thermal_list_lock);
|
|
|
|
mutex_destroy(&thermal_governor_lock);
|
2008-01-17 10:51:08 +03:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit thermal_exit(void)
|
|
|
|
{
|
2015-10-30 11:31:58 +03:00
|
|
|
unregister_pm_notifier(&thermal_pm_nb);
|
2013-07-03 23:35:39 +04:00
|
|
|
of_thermal_destroy_zones();
|
2013-03-26 12:38:29 +04:00
|
|
|
genetlink_exit();
|
2008-01-17 10:51:08 +03:00
|
|
|
class_unregister(&thermal_class);
|
2013-03-26 12:38:29 +04:00
|
|
|
thermal_unregister_governors();
|
2008-01-17 10:51:08 +03:00
|
|
|
idr_destroy(&thermal_tz_idr);
|
|
|
|
idr_destroy(&thermal_cdev_idr);
|
|
|
|
mutex_destroy(&thermal_idr_lock);
|
|
|
|
mutex_destroy(&thermal_list_lock);
|
2013-03-26 12:38:29 +04:00
|
|
|
mutex_destroy(&thermal_governor_lock);
|
2008-01-17 10:51:08 +03:00
|
|
|
}
|
|
|
|
|
2010-10-27 02:03:29 +04:00
|
|
|
fs_initcall(thermal_init);
|
2008-01-17 10:51:08 +03:00
|
|
|
module_exit(thermal_exit);
|