2018-05-07 17:52:29 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2008-01-17 07:51:08 +00: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>
|
|
|
|
*/
|
|
|
|
|
2012-03-21 19:55:02 +00:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/err.h>
|
2020-05-11 12:24:50 +00:00
|
|
|
#include <linux/export.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 08:04:11 +00:00
|
|
|
#include <linux/slab.h>
|
2008-01-17 07:51:08 +00:00
|
|
|
#include <linux/kdev_t.h>
|
|
|
|
#include <linux/idr.h>
|
2024-04-02 19:03:36 +00:00
|
|
|
#include <linux/list_sort.h>
|
2008-01-17 07:51:08 +00:00
|
|
|
#include <linux/thermal.h>
|
2008-12-03 17:55:32 +00:00
|
|
|
#include <linux/reboot.h>
|
2013-05-17 11:52:02 +00:00
|
|
|
#include <linux/string.h>
|
2013-09-26 19:55:01 +00:00
|
|
|
#include <linux/of.h>
|
2015-10-30 08:31:58 +00:00
|
|
|
#include <linux/suspend.h>
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2014-07-29 10:50:48 +00:00
|
|
|
#define CREATE_TRACE_POINTS
|
2023-03-07 13:37:25 +00:00
|
|
|
#include "thermal_trace.h"
|
2014-07-29 10:50:48 +00:00
|
|
|
|
2012-09-18 05:34:53 +00:00
|
|
|
#include "thermal_core.h"
|
2013-07-03 19:14:28 +00:00
|
|
|
#include "thermal_hwmon.h"
|
2012-09-18 05:34:53 +00:00
|
|
|
|
2016-12-21 17:47:03 +00:00
|
|
|
static DEFINE_IDA(thermal_tz_ida);
|
|
|
|
static DEFINE_IDA(thermal_cdev_ida);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
static LIST_HEAD(thermal_tz_list);
|
|
|
|
static LIST_HEAD(thermal_cdev_list);
|
2012-09-18 05:34:57 +00:00
|
|
|
static LIST_HEAD(thermal_governor_list);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
static DEFINE_MUTEX(thermal_list_lock);
|
2012-09-18 05:34:57 +00:00
|
|
|
static DEFINE_MUTEX(thermal_governor_lock);
|
|
|
|
|
2014-01-24 02:23:19 +00:00
|
|
|
static struct thermal_governor *def_governor;
|
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
/*
|
|
|
|
* Governor section: set of functions to handle thermal governors
|
|
|
|
*
|
|
|
|
* Functions to help in the life cycle of thermal governors within
|
|
|
|
* the thermal core and by the thermal governor code.
|
|
|
|
*/
|
|
|
|
|
2012-09-18 05:34:57 +00:00
|
|
|
static struct thermal_governor *__find_governor(const char *name)
|
|
|
|
{
|
|
|
|
struct thermal_governor *pos;
|
|
|
|
|
2014-01-24 02:23:19 +00:00
|
|
|
if (!name || !name[0])
|
|
|
|
return def_governor;
|
|
|
|
|
2012-09-18 05:34:57 +00:00
|
|
|
list_for_each_entry(pos, &thermal_governor_list, governor_list)
|
2014-10-13 22:55:01 +00:00
|
|
|
if (!strncasecmp(name, pos->name, THERMAL_NAME_LENGTH))
|
2012-09-18 05:34:57 +00:00
|
|
|
return pos;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-26 19:00:27 +00: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 05:34:57 +00: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;
|
2016-11-08 05:09:20 +00:00
|
|
|
if (!__find_governor(governor->name)) {
|
2016-11-08 05:09:21 +00:00
|
|
|
bool match_default;
|
|
|
|
|
2012-09-18 05:34:57 +00:00
|
|
|
err = 0;
|
|
|
|
list_add(&governor->governor_list, &thermal_governor_list);
|
2016-11-08 05:09:21 +00:00
|
|
|
match_default = !strncmp(governor->name,
|
|
|
|
DEFAULT_THERMAL_GOVERNOR,
|
|
|
|
THERMAL_NAME_LENGTH);
|
|
|
|
|
|
|
|
if (!def_governor && match_default)
|
2014-01-24 02:23:19 +00:00
|
|
|
def_governor = governor;
|
2012-09-18 05:34:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
2014-01-24 02:23:19 +00:00
|
|
|
/*
|
|
|
|
* only thermal zones with specified tz->tzp->governor_name
|
|
|
|
* may run with tz->govenor unset
|
|
|
|
*/
|
2012-09-18 05:34:57 +00:00
|
|
|
if (pos->governor)
|
|
|
|
continue;
|
2014-01-24 02:23:19 +00:00
|
|
|
|
|
|
|
name = pos->tzp->governor_name;
|
|
|
|
|
2015-02-26 19:00:27 +00: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 05:34:57 +00: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);
|
|
|
|
|
2016-11-08 05:09:20 +00:00
|
|
|
if (!__find_governor(governor->name))
|
2012-09-18 05:34:57 +00:00
|
|
|
goto exit;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
2014-10-13 22:55:01 +00:00
|
|
|
if (!strncasecmp(pos->governor->name, governor->name,
|
2016-11-08 05:09:21 +00:00
|
|
|
THERMAL_NAME_LENGTH))
|
2015-02-26 19:00:27 +00:00
|
|
|
thermal_set_governor(pos, NULL);
|
2012-09-18 05:34:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
list_del(&governor->governor_list);
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
2012-09-18 05:34:54 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
|
|
|
|
char *policy)
|
2012-09-18 05:34:54 +00:00
|
|
|
{
|
2016-11-08 05:09:05 +00:00
|
|
|
struct thermal_governor *gov;
|
|
|
|
int ret = -EINVAL;
|
2012-09-18 05:34:54 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
mutex_lock(&thermal_governor_lock);
|
2012-09-18 05:34:54 +00:00
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
gov = __find_governor(strim(policy));
|
|
|
|
if (!gov)
|
|
|
|
goto exit;
|
2012-09-18 05:34:54 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
ret = thermal_set_governor(tz, gov);
|
2012-09-18 05:34:54 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
exit:
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
2012-09-18 05:34:54 +00:00
|
|
|
|
2024-01-03 11:59:10 +00:00
|
|
|
thermal_notify_tz_gov_change(tz, policy);
|
2020-07-06 10:55:38 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
return ret;
|
2012-09-18 05:34:59 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
int thermal_build_list_of_policies(char *buf)
|
2012-09-18 05:34:59 +00:00
|
|
|
{
|
2016-11-08 05:09:05 +00:00
|
|
|
struct thermal_governor *pos;
|
|
|
|
ssize_t count = 0;
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
mutex_lock(&thermal_governor_lock);
|
2013-07-16 19:26:28 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
list_for_each_entry(pos, &thermal_governor_list, governor_list) {
|
2023-01-17 02:40:26 +00:00
|
|
|
count += sysfs_emit_at(buf, count, "%s ", pos->name);
|
2012-09-18 05:34:59 +00:00
|
|
|
}
|
2023-01-17 02:40:26 +00:00
|
|
|
count += sysfs_emit_at(buf, count, "\n");
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
mutex_unlock(&thermal_governor_lock);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2016-11-08 05:09:05 +00:00
|
|
|
return count;
|
2012-09-18 05:34:59 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
static void __init thermal_unregister_governors(void)
|
2012-09-18 05:34:59 +00:00
|
|
|
{
|
2019-06-12 20:13:25 +00:00
|
|
|
struct thermal_governor **governor;
|
|
|
|
|
|
|
|
for_each_governor_table(governor)
|
|
|
|
thermal_unregister_governor(*governor);
|
|
|
|
}
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
static int __init thermal_register_governors(void)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
struct thermal_governor **governor;
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
for_each_governor_table(governor) {
|
|
|
|
ret = thermal_register_governor(*governor);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("Failed to register governor: '%s'",
|
|
|
|
(*governor)->name);
|
|
|
|
break;
|
|
|
|
}
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
pr_info("Registered thermal governor '%s'",
|
|
|
|
(*governor)->name);
|
|
|
|
}
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
if (ret) {
|
|
|
|
struct thermal_governor **gov;
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
for_each_governor_table(gov) {
|
|
|
|
if (gov == governor)
|
|
|
|
break;
|
|
|
|
thermal_unregister_governor(*gov);
|
|
|
|
}
|
|
|
|
}
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2019-06-12 20:13:25 +00:00
|
|
|
return ret;
|
2012-09-18 05:34:59 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 05:09:15 +00:00
|
|
|
/*
|
|
|
|
* Zone update section: main control loop applied to each zone while monitoring
|
|
|
|
* in polling mode. The monitoring is done using a workqueue.
|
|
|
|
* Same update may be done on a zone by calling thermal_zone_device_update().
|
|
|
|
*
|
|
|
|
* An update means:
|
|
|
|
* - Non-critical trips will invoke the governor responsible for that zone;
|
|
|
|
* - Hot trips will produce a notification to userspace;
|
|
|
|
* - Critical trip point will cause a system shutdown.
|
|
|
|
*/
|
2012-09-18 05:35:04 +00:00
|
|
|
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
|
2020-12-16 22:03:36 +00:00
|
|
|
unsigned long delay)
|
2012-09-18 05:35:04 +00:00
|
|
|
{
|
2020-12-16 22:03:36 +00:00
|
|
|
if (delay)
|
2018-09-04 07:14:08 +00:00
|
|
|
mod_delayed_work(system_freezable_power_efficient_wq,
|
2020-12-16 22:03:36 +00:00
|
|
|
&tz->poll_queue, delay);
|
2012-09-18 05:35:04 +00:00
|
|
|
else
|
2019-11-12 20:42:23 +00:00
|
|
|
cancel_delayed_work(&tz->poll_queue);
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void monitor_thermal_zone(struct thermal_zone_device *tz)
|
|
|
|
{
|
2022-08-05 15:38:31 +00:00
|
|
|
if (tz->mode != THERMAL_DEVICE_ENABLED)
|
|
|
|
thermal_zone_device_set_polling(tz, 0);
|
thermal: core: Move passive polling management to the core
Passive polling is enabled by setting the 'passive' field in
struct thermal_zone_device to a positive value so long as the
'passive_delay_jiffies' field is greater than zero. It causes
the thermal core to actively check the thermal zone temperature
periodically which in theory should be done after crossing a
passive trip point on the way up in order to allow governors to
react more rapidly to temperature changes and adjust mitigation
more precisely.
However, the 'passive' field in struct thermal_zone_device is currently
managed by governors which is quite problematic. First of all, only
two governors, Step-Wise and Power Allocator, update that field at
all, so the other governors do not benefit from passive polling,
although in principle they should. Moreover, if the zone governor is
changed from, say, Step-Wise to Fair-Share after 'passive' has been
incremented by the former, it is not going to be reset back to zero by
the latter even if the zone temperature falls down below all passive
trip points.
For this reason, make handle_thermal_trip() increment 'passive'
to enable passive polling for the given thermal zone whenever a
passive trip point is crossed on the way up and decrement it
whenever a passive trip point is crossed on the way down. Also
remove the 'passive' field updates from governors and additionally
clear it in thermal_zone_device_init() to prevent passive polling
from being enabled after a system resume just beacuse it was enabled
before suspending the system.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Lukasz Luba <lukasz.luba@arm.com>
Tested-by: Lukasz Luba <lukasz.luba@arm.com>
2024-04-30 15:52:33 +00:00
|
|
|
else if (tz->passive > 0)
|
2020-12-16 22:03:36 +00:00
|
|
|
thermal_zone_device_set_polling(tz, tz->passive_delay_jiffies);
|
2022-08-05 15:38:31 +00:00
|
|
|
else if (tz->polling_delay_jiffies)
|
2020-12-16 22:03:36 +00:00
|
|
|
thermal_zone_device_set_polling(tz, tz->polling_delay_jiffies);
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
2024-04-10 16:10:53 +00:00
|
|
|
static struct thermal_governor *thermal_get_tz_governor(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
if (tz->governor)
|
|
|
|
return tz->governor;
|
|
|
|
|
|
|
|
return def_governor;
|
|
|
|
}
|
|
|
|
|
2023-12-20 23:17:45 +00:00
|
|
|
void thermal_governor_update_tz(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_notify_event reason)
|
|
|
|
{
|
|
|
|
if (!tz->governor || !tz->governor->update_tz)
|
|
|
|
return;
|
|
|
|
|
|
|
|
tz->governor->update_tz(tz, reason);
|
|
|
|
}
|
|
|
|
|
2023-11-29 12:43:28 +00:00
|
|
|
static void thermal_zone_device_halt(struct thermal_zone_device *tz, bool shutdown)
|
2017-04-18 04:29:59 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* poweroff_delay_ms must be a carefully profiled positive value.
|
2021-06-03 05:41:03 +00:00
|
|
|
* Its a must for forced_emergency_poweroff_work to be scheduled.
|
2017-04-18 04:29:59 +00:00
|
|
|
*/
|
2021-06-03 05:41:03 +00:00
|
|
|
int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
|
2023-11-29 12:43:28 +00:00
|
|
|
const char *msg = "Temperature too high";
|
2017-04-18 04:29:59 +00:00
|
|
|
|
2023-11-29 12:43:28 +00:00
|
|
|
dev_emerg(&tz->device, "%s: critical temperature reached\n", tz->type);
|
2020-12-10 12:15:11 +00:00
|
|
|
|
2023-11-29 12:43:28 +00:00
|
|
|
if (shutdown)
|
|
|
|
hw_protection_shutdown(msg, poweroff_delay_ms);
|
2023-11-29 12:43:29 +00:00
|
|
|
else
|
|
|
|
hw_protection_reboot(msg, poweroff_delay_ms);
|
2023-11-29 12:43:28 +00:00
|
|
|
}
|
2020-12-10 12:15:11 +00:00
|
|
|
|
2023-11-29 12:43:28 +00:00
|
|
|
void thermal_zone_device_critical(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
thermal_zone_device_halt(tz, true);
|
2020-12-10 12:15:11 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(thermal_zone_device_critical);
|
|
|
|
|
2023-11-29 12:43:29 +00:00
|
|
|
void thermal_zone_device_critical_reboot(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
thermal_zone_device_halt(tz, false);
|
|
|
|
}
|
|
|
|
|
2012-09-18 05:35:04 +00:00
|
|
|
static void handle_critical_trips(struct thermal_zone_device *tz,
|
2023-10-12 18:34:50 +00:00
|
|
|
const struct thermal_trip *trip)
|
2012-09-18 05:35:04 +00:00
|
|
|
{
|
2023-10-12 18:34:50 +00:00
|
|
|
trace_thermal_zone_trip(tz, thermal_zone_trip_id(tz, trip), trip->type);
|
2014-07-29 10:50:50 +00:00
|
|
|
|
2023-10-12 18:34:50 +00:00
|
|
|
if (trip->type == THERMAL_TRIP_CRITICAL)
|
2024-02-22 17:18:01 +00:00
|
|
|
tz->ops.critical(tz);
|
|
|
|
else if (tz->ops.hot)
|
|
|
|
tz->ops.hot(tz);
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 18:34:50 +00:00
|
|
|
static void handle_thermal_trip(struct thermal_zone_device *tz,
|
2024-04-02 19:03:36 +00:00
|
|
|
struct thermal_trip_desc *td,
|
|
|
|
struct list_head *way_up_list,
|
|
|
|
struct list_head *way_down_list)
|
2012-09-18 05:35:04 +00:00
|
|
|
{
|
2024-04-02 18:56:43 +00:00
|
|
|
const struct thermal_trip *trip = &td->trip;
|
2024-04-02 19:02:10 +00:00
|
|
|
int old_threshold;
|
2024-04-02 18:56:43 +00:00
|
|
|
|
2023-10-12 18:34:50 +00:00
|
|
|
if (trip->temperature == THERMAL_TEMP_INVALID)
|
2023-09-14 19:42:20 +00:00
|
|
|
return;
|
|
|
|
|
2024-04-02 18:59:01 +00:00
|
|
|
/*
|
|
|
|
* If the trip temperature or hysteresis has been updated recently,
|
|
|
|
* the threshold needs to be computed again using the new values.
|
|
|
|
* However, its initial value still reflects the old ones and that
|
|
|
|
* is what needs to be compared with the previous zone temperature
|
|
|
|
* to decide which action to take.
|
|
|
|
*/
|
2024-04-02 19:02:10 +00:00
|
|
|
old_threshold = td->threshold;
|
|
|
|
td->threshold = trip->temperature;
|
|
|
|
|
|
|
|
if (tz->last_temperature >= old_threshold &&
|
|
|
|
tz->last_temperature != THERMAL_TEMP_INVALID) {
|
thermal: core: Add trip thresholds for trip crossing detection
The trip crossing detection in handle_thermal_trip() does not work
correctly in the cases when a trip point is crossed on the way up and
then the zone temperature stays above its low temperature (that is, its
temperature decreased by its hysteresis). The trip temperature may
be passed by the zone temperature subsequently in that case, even
multiple times, but that does not count as the trip crossing as long as
the zone temperature does not fall below the trip's low temperature or,
in other words, until the trip is crossed on the way down.
|-----------low--------high------------|
|<--------->|
| hyst |
| |
| -|--> crossed on the way up
|
<---|-- crossed on the way down
However, handle_thermal_trip() will invoke thermal_notify_tz_trip_up()
every time the trip temperature is passed by the zone temperature on
the way up regardless of whether or not the trip has been crossed on
the way down yet. Moreover, it will not call thermal_notify_tz_trip_down()
if the last zone temperature was between the trip's temperature and its
low temperature, so some "trip crossed on the way down" events may not
be reported.
To address this issue, introduce trip thresholds equal to either the
temperature of the given trip, or its low temperature, such that if
the trip's threshold is passed by the zone temperature on the way up,
its value will be set to the trip's low temperature and
thermal_notify_tz_trip_up() will be called, and if the trip's threshold
is passed by the zone temperature on the way down, its value will be set
to the trip's temperature (high) and thermal_notify_tz_trip_down() will
be called. Accordingly, if the threshold is passed on the way up, it
cannot be passed on the way up again until its passed on the way down
and if it is passed on the way down, it cannot be passed on the way down
again until it is passed on the way up which guarantees correct
triggering of trip crossing notifications.
If the last temperature of the zone is invalid, the trip's threshold
will be set depending of the zone's current temperature: If that
temperature is above the trip's temperature, its threshold will be
set to its low temperature or otherwise its threshold will be set to
its (high) temperature. Because the zone temperature is initially
set to invalid and tz->last_temperature is only updated by
update_temperature(), this is sufficient to set the correct initial
threshold values for all trips.
Link: https://lore.kernel.org/all/20220718145038.1114379-4-daniel.lezcano@linaro.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2023-11-09 15:01:48 +00:00
|
|
|
/*
|
2024-04-02 18:59:01 +00:00
|
|
|
* Mitigation is under way, so it needs to stop if the zone
|
|
|
|
* temperature falls below the low temperature of the trip.
|
|
|
|
* In that case, the trip temperature becomes the new threshold.
|
thermal: core: Add trip thresholds for trip crossing detection
The trip crossing detection in handle_thermal_trip() does not work
correctly in the cases when a trip point is crossed on the way up and
then the zone temperature stays above its low temperature (that is, its
temperature decreased by its hysteresis). The trip temperature may
be passed by the zone temperature subsequently in that case, even
multiple times, but that does not count as the trip crossing as long as
the zone temperature does not fall below the trip's low temperature or,
in other words, until the trip is crossed on the way down.
|-----------low--------high------------|
|<--------->|
| hyst |
| |
| -|--> crossed on the way up
|
<---|-- crossed on the way down
However, handle_thermal_trip() will invoke thermal_notify_tz_trip_up()
every time the trip temperature is passed by the zone temperature on
the way up regardless of whether or not the trip has been crossed on
the way down yet. Moreover, it will not call thermal_notify_tz_trip_down()
if the last zone temperature was between the trip's temperature and its
low temperature, so some "trip crossed on the way down" events may not
be reported.
To address this issue, introduce trip thresholds equal to either the
temperature of the given trip, or its low temperature, such that if
the trip's threshold is passed by the zone temperature on the way up,
its value will be set to the trip's low temperature and
thermal_notify_tz_trip_up() will be called, and if the trip's threshold
is passed by the zone temperature on the way down, its value will be set
to the trip's temperature (high) and thermal_notify_tz_trip_down() will
be called. Accordingly, if the threshold is passed on the way up, it
cannot be passed on the way up again until its passed on the way down
and if it is passed on the way down, it cannot be passed on the way down
again until it is passed on the way up which guarantees correct
triggering of trip crossing notifications.
If the last temperature of the zone is invalid, the trip's threshold
will be set depending of the zone's current temperature: If that
temperature is above the trip's temperature, its threshold will be
set to its low temperature or otherwise its threshold will be set to
its (high) temperature. Because the zone temperature is initially
set to invalid and tz->last_temperature is only updated by
update_temperature(), this is sufficient to set the correct initial
threshold values for all trips.
Link: https://lore.kernel.org/all/20220718145038.1114379-4-daniel.lezcano@linaro.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2023-11-09 15:01:48 +00:00
|
|
|
*/
|
|
|
|
if (tz->temperature < trip->temperature - trip->hysteresis) {
|
2024-04-02 19:03:36 +00:00
|
|
|
list_add(&td->notify_list_node, way_down_list);
|
|
|
|
td->notify_temp = trip->temperature - trip->hysteresis;
|
thermal: core: Move passive polling management to the core
Passive polling is enabled by setting the 'passive' field in
struct thermal_zone_device to a positive value so long as the
'passive_delay_jiffies' field is greater than zero. It causes
the thermal core to actively check the thermal zone temperature
periodically which in theory should be done after crossing a
passive trip point on the way up in order to allow governors to
react more rapidly to temperature changes and adjust mitigation
more precisely.
However, the 'passive' field in struct thermal_zone_device is currently
managed by governors which is quite problematic. First of all, only
two governors, Step-Wise and Power Allocator, update that field at
all, so the other governors do not benefit from passive polling,
although in principle they should. Moreover, if the zone governor is
changed from, say, Step-Wise to Fair-Share after 'passive' has been
incremented by the former, it is not going to be reset back to zero by
the latter even if the zone temperature falls down below all passive
trip points.
For this reason, make handle_thermal_trip() increment 'passive'
to enable passive polling for the given thermal zone whenever a
passive trip point is crossed on the way up and decrement it
whenever a passive trip point is crossed on the way down. Also
remove the 'passive' field updates from governors and additionally
clear it in thermal_zone_device_init() to prevent passive polling
from being enabled after a system resume just beacuse it was enabled
before suspending the system.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Lukasz Luba <lukasz.luba@arm.com>
Tested-by: Lukasz Luba <lukasz.luba@arm.com>
2024-04-30 15:52:33 +00:00
|
|
|
|
|
|
|
if (trip->type == THERMAL_TRIP_PASSIVE) {
|
|
|
|
tz->passive--;
|
|
|
|
WARN_ON(tz->passive < 0);
|
|
|
|
}
|
thermal: core: Add trip thresholds for trip crossing detection
The trip crossing detection in handle_thermal_trip() does not work
correctly in the cases when a trip point is crossed on the way up and
then the zone temperature stays above its low temperature (that is, its
temperature decreased by its hysteresis). The trip temperature may
be passed by the zone temperature subsequently in that case, even
multiple times, but that does not count as the trip crossing as long as
the zone temperature does not fall below the trip's low temperature or,
in other words, until the trip is crossed on the way down.
|-----------low--------high------------|
|<--------->|
| hyst |
| |
| -|--> crossed on the way up
|
<---|-- crossed on the way down
However, handle_thermal_trip() will invoke thermal_notify_tz_trip_up()
every time the trip temperature is passed by the zone temperature on
the way up regardless of whether or not the trip has been crossed on
the way down yet. Moreover, it will not call thermal_notify_tz_trip_down()
if the last zone temperature was between the trip's temperature and its
low temperature, so some "trip crossed on the way down" events may not
be reported.
To address this issue, introduce trip thresholds equal to either the
temperature of the given trip, or its low temperature, such that if
the trip's threshold is passed by the zone temperature on the way up,
its value will be set to the trip's low temperature and
thermal_notify_tz_trip_up() will be called, and if the trip's threshold
is passed by the zone temperature on the way down, its value will be set
to the trip's temperature (high) and thermal_notify_tz_trip_down() will
be called. Accordingly, if the threshold is passed on the way up, it
cannot be passed on the way up again until its passed on the way down
and if it is passed on the way down, it cannot be passed on the way down
again until it is passed on the way up which guarantees correct
triggering of trip crossing notifications.
If the last temperature of the zone is invalid, the trip's threshold
will be set depending of the zone's current temperature: If that
temperature is above the trip's temperature, its threshold will be
set to its low temperature or otherwise its threshold will be set to
its (high) temperature. Because the zone temperature is initially
set to invalid and tz->last_temperature is only updated by
update_temperature(), this is sufficient to set the correct initial
threshold values for all trips.
Link: https://lore.kernel.org/all/20220718145038.1114379-4-daniel.lezcano@linaro.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2023-11-09 15:01:48 +00:00
|
|
|
} else {
|
2024-04-02 19:02:10 +00:00
|
|
|
td->threshold -= trip->hysteresis;
|
thermal: core: Add trip thresholds for trip crossing detection
The trip crossing detection in handle_thermal_trip() does not work
correctly in the cases when a trip point is crossed on the way up and
then the zone temperature stays above its low temperature (that is, its
temperature decreased by its hysteresis). The trip temperature may
be passed by the zone temperature subsequently in that case, even
multiple times, but that does not count as the trip crossing as long as
the zone temperature does not fall below the trip's low temperature or,
in other words, until the trip is crossed on the way down.
|-----------low--------high------------|
|<--------->|
| hyst |
| |
| -|--> crossed on the way up
|
<---|-- crossed on the way down
However, handle_thermal_trip() will invoke thermal_notify_tz_trip_up()
every time the trip temperature is passed by the zone temperature on
the way up regardless of whether or not the trip has been crossed on
the way down yet. Moreover, it will not call thermal_notify_tz_trip_down()
if the last zone temperature was between the trip's temperature and its
low temperature, so some "trip crossed on the way down" events may not
be reported.
To address this issue, introduce trip thresholds equal to either the
temperature of the given trip, or its low temperature, such that if
the trip's threshold is passed by the zone temperature on the way up,
its value will be set to the trip's low temperature and
thermal_notify_tz_trip_up() will be called, and if the trip's threshold
is passed by the zone temperature on the way down, its value will be set
to the trip's temperature (high) and thermal_notify_tz_trip_down() will
be called. Accordingly, if the threshold is passed on the way up, it
cannot be passed on the way up again until its passed on the way down
and if it is passed on the way down, it cannot be passed on the way down
again until it is passed on the way up which guarantees correct
triggering of trip crossing notifications.
If the last temperature of the zone is invalid, the trip's threshold
will be set depending of the zone's current temperature: If that
temperature is above the trip's temperature, its threshold will be
set to its low temperature or otherwise its threshold will be set to
its (high) temperature. Because the zone temperature is initially
set to invalid and tz->last_temperature is only updated by
update_temperature(), this is sufficient to set the correct initial
threshold values for all trips.
Link: https://lore.kernel.org/all/20220718145038.1114379-4-daniel.lezcano@linaro.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2023-11-09 15:01:48 +00:00
|
|
|
}
|
2024-04-02 19:02:10 +00:00
|
|
|
} else if (tz->temperature >= trip->temperature) {
|
|
|
|
/*
|
|
|
|
* There is no mitigation under way, so it needs to be started
|
|
|
|
* if the zone temperature exceeds the trip one. The new
|
|
|
|
* threshold is then set to the low temperature of the trip.
|
|
|
|
*/
|
2024-04-02 19:03:36 +00:00
|
|
|
list_add_tail(&td->notify_list_node, way_up_list);
|
|
|
|
td->notify_temp = trip->temperature;
|
2024-04-02 19:02:10 +00:00
|
|
|
td->threshold -= trip->hysteresis;
|
2012-09-18 05:35:04 +00:00
|
|
|
|
thermal: core: Move passive polling management to the core
Passive polling is enabled by setting the 'passive' field in
struct thermal_zone_device to a positive value so long as the
'passive_delay_jiffies' field is greater than zero. It causes
the thermal core to actively check the thermal zone temperature
periodically which in theory should be done after crossing a
passive trip point on the way up in order to allow governors to
react more rapidly to temperature changes and adjust mitigation
more precisely.
However, the 'passive' field in struct thermal_zone_device is currently
managed by governors which is quite problematic. First of all, only
two governors, Step-Wise and Power Allocator, update that field at
all, so the other governors do not benefit from passive polling,
although in principle they should. Moreover, if the zone governor is
changed from, say, Step-Wise to Fair-Share after 'passive' has been
incremented by the former, it is not going to be reset back to zero by
the latter even if the zone temperature falls down below all passive
trip points.
For this reason, make handle_thermal_trip() increment 'passive'
to enable passive polling for the given thermal zone whenever a
passive trip point is crossed on the way up and decrement it
whenever a passive trip point is crossed on the way down. Also
remove the 'passive' field updates from governors and additionally
clear it in thermal_zone_device_init() to prevent passive polling
from being enabled after a system resume just beacuse it was enabled
before suspending the system.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Lukasz Luba <lukasz.luba@arm.com>
Tested-by: Lukasz Luba <lukasz.luba@arm.com>
2024-04-30 15:52:33 +00:00
|
|
|
if (trip->type == THERMAL_TRIP_PASSIVE)
|
|
|
|
tz->passive++;
|
|
|
|
else if (trip->type == THERMAL_TRIP_CRITICAL ||
|
|
|
|
trip->type == THERMAL_TRIP_HOT)
|
2024-04-10 17:44:34 +00:00
|
|
|
handle_critical_trips(tz, trip);
|
|
|
|
}
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void update_temperature(struct thermal_zone_device *tz)
|
|
|
|
{
|
2015-07-24 06:12:54 +00:00
|
|
|
int temp, ret;
|
2012-09-18 05:35:04 +00:00
|
|
|
|
2022-08-05 15:38:34 +00:00
|
|
|
ret = __thermal_zone_get_temp(tz, &temp);
|
2012-09-18 05:35:04 +00:00
|
|
|
if (ret) {
|
2015-03-21 14:02:55 +00:00
|
|
|
if (ret != -EAGAIN)
|
|
|
|
dev_warn(&tz->device,
|
|
|
|
"failed to read out thermal zone (%d)\n",
|
|
|
|
ret);
|
2013-02-04 00:30:15 +00:00
|
|
|
return;
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tz->last_temperature = tz->temperature;
|
|
|
|
tz->temperature = temp;
|
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 05:54:26 +00:00
|
|
|
|
2014-07-29 10:50:48 +00:00
|
|
|
trace_thermal_temperature(tz);
|
2020-07-06 10:55:38 +00:00
|
|
|
|
|
|
|
thermal_genl_sampling_temp(tz->id, temp);
|
2015-10-30 08:31:47 +00:00
|
|
|
}
|
|
|
|
|
2023-12-18 19:26:47 +00: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);
|
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
|
|
|
}
|
|
|
|
|
2018-11-07 22:36:11 +00:00
|
|
|
static void thermal_zone_device_init(struct thermal_zone_device *tz)
|
2015-10-30 08:31:47 +00:00
|
|
|
{
|
|
|
|
struct thermal_instance *pos;
|
2023-12-18 19:26:47 +00:00
|
|
|
|
|
|
|
INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
|
|
|
|
|
2015-10-30 08:31:47 +00:00
|
|
|
tz->temperature = THERMAL_TEMP_INVALID;
|
thermal: core: Move passive polling management to the core
Passive polling is enabled by setting the 'passive' field in
struct thermal_zone_device to a positive value so long as the
'passive_delay_jiffies' field is greater than zero. It causes
the thermal core to actively check the thermal zone temperature
periodically which in theory should be done after crossing a
passive trip point on the way up in order to allow governors to
react more rapidly to temperature changes and adjust mitigation
more precisely.
However, the 'passive' field in struct thermal_zone_device is currently
managed by governors which is quite problematic. First of all, only
two governors, Step-Wise and Power Allocator, update that field at
all, so the other governors do not benefit from passive polling,
although in principle they should. Moreover, if the zone governor is
changed from, say, Step-Wise to Fair-Share after 'passive' has been
incremented by the former, it is not going to be reset back to zero by
the latter even if the zone temperature falls down below all passive
trip points.
For this reason, make handle_thermal_trip() increment 'passive'
to enable passive polling for the given thermal zone whenever a
passive trip point is crossed on the way up and decrement it
whenever a passive trip point is crossed on the way down. Also
remove the 'passive' field updates from governors and additionally
clear it in thermal_zone_device_init() to prevent passive polling
from being enabled after a system resume just beacuse it was enabled
before suspending the system.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Lukasz Luba <lukasz.luba@arm.com>
Tested-by: Lukasz Luba <lukasz.luba@arm.com>
2024-04-30 15:52:33 +00:00
|
|
|
tz->passive = 0;
|
2021-11-02 20:00:40 +00:00
|
|
|
tz->prev_low_trip = -INT_MAX;
|
|
|
|
tz->prev_high_trip = INT_MAX;
|
2015-10-30 08:31:47 +00:00
|
|
|
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
|
|
|
|
pos->initialized = false;
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
2024-04-23 19:01:15 +00:00
|
|
|
static void thermal_governor_trip_crossed(struct thermal_governor *governor,
|
|
|
|
struct thermal_zone_device *tz,
|
|
|
|
const struct thermal_trip *trip,
|
|
|
|
bool crossed_up)
|
|
|
|
{
|
|
|
|
if (governor->trip_crossed)
|
|
|
|
governor->trip_crossed(tz, trip, crossed_up);
|
|
|
|
}
|
|
|
|
|
2024-04-02 19:03:36 +00:00
|
|
|
static int thermal_trip_notify_cmp(void *ascending, const struct list_head *a,
|
|
|
|
const struct list_head *b)
|
|
|
|
{
|
|
|
|
struct thermal_trip_desc *tda = container_of(a, struct thermal_trip_desc,
|
|
|
|
notify_list_node);
|
|
|
|
struct thermal_trip_desc *tdb = container_of(b, struct thermal_trip_desc,
|
|
|
|
notify_list_node);
|
|
|
|
int ret = tdb->notify_temp - tda->notify_temp;
|
|
|
|
|
|
|
|
return ascending ? ret : -ret;
|
|
|
|
}
|
|
|
|
|
2022-11-10 15:24:58 +00:00
|
|
|
void __thermal_zone_device_update(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_notify_event event)
|
2022-11-10 15:24:56 +00:00
|
|
|
{
|
2024-04-10 16:10:53 +00:00
|
|
|
struct thermal_governor *governor = thermal_get_tz_governor(tz);
|
2024-04-02 18:56:43 +00:00
|
|
|
struct thermal_trip_desc *td;
|
2024-04-02 19:03:36 +00:00
|
|
|
LIST_HEAD(way_down_list);
|
|
|
|
LIST_HEAD(way_up_list);
|
2022-11-10 15:24:56 +00:00
|
|
|
|
2023-12-18 19:25:02 +00:00
|
|
|
if (tz->suspended)
|
2022-11-10 15:24:56 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!thermal_zone_device_is_enabled(tz))
|
|
|
|
return;
|
|
|
|
|
|
|
|
update_temperature(tz);
|
|
|
|
|
2024-04-30 15:45:55 +00:00
|
|
|
if (tz->temperature == THERMAL_TEMP_INVALID)
|
|
|
|
return;
|
|
|
|
|
2022-11-10 15:24:56 +00:00
|
|
|
__thermal_zone_set_trips(tz);
|
|
|
|
|
|
|
|
tz->notify_event = event;
|
|
|
|
|
2024-04-02 18:56:43 +00:00
|
|
|
for_each_trip_desc(tz, td)
|
2024-04-02 19:03:36 +00:00
|
|
|
handle_thermal_trip(tz, td, &way_up_list, &way_down_list);
|
|
|
|
|
|
|
|
list_sort(&way_up_list, &way_up_list, thermal_trip_notify_cmp);
|
|
|
|
list_for_each_entry(td, &way_up_list, notify_list_node) {
|
|
|
|
thermal_notify_tz_trip_up(tz, &td->trip);
|
|
|
|
thermal_debug_tz_trip_up(tz, &td->trip);
|
2024-04-23 19:01:15 +00:00
|
|
|
thermal_governor_trip_crossed(governor, tz, &td->trip, true);
|
2024-04-02 19:03:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
list_sort(NULL, &way_down_list, thermal_trip_notify_cmp);
|
|
|
|
list_for_each_entry(td, &way_down_list, notify_list_node) {
|
|
|
|
thermal_notify_tz_trip_down(tz, &td->trip);
|
|
|
|
thermal_debug_tz_trip_down(tz, &td->trip);
|
2024-04-23 19:01:15 +00:00
|
|
|
thermal_governor_trip_crossed(governor, tz, &td->trip, false);
|
2024-04-02 19:03:36 +00:00
|
|
|
}
|
2022-11-10 15:24:56 +00:00
|
|
|
|
2024-04-10 16:08:12 +00:00
|
|
|
if (governor->manage)
|
|
|
|
governor->manage(tz);
|
|
|
|
|
2024-04-17 13:11:50 +00:00
|
|
|
thermal_debug_update_trip_stats(tz);
|
2024-04-17 13:09:46 +00:00
|
|
|
|
2022-11-10 15:24:56 +00:00
|
|
|
monitor_thermal_zone(tz);
|
|
|
|
}
|
|
|
|
|
2020-06-29 12:29:20 +00:00
|
|
|
static int thermal_zone_device_set_mode(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_device_mode mode)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
/* do nothing if mode isn't changing */
|
|
|
|
if (mode == tz->mode) {
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-02-22 17:18:01 +00:00
|
|
|
if (tz->ops.change_mode)
|
|
|
|
ret = tz->ops.change_mode(tz, mode);
|
2020-06-29 12:29:20 +00:00
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
tz->mode = mode;
|
|
|
|
|
2022-11-10 15:24:56 +00:00
|
|
|
__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2020-06-29 12:29:20 +00:00
|
|
|
|
2022-11-10 15:24:56 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
2020-06-29 12:29:20 +00:00
|
|
|
|
2020-07-27 23:10:33 +00:00
|
|
|
if (mode == THERMAL_DEVICE_ENABLED)
|
2024-01-03 11:59:10 +00:00
|
|
|
thermal_notify_tz_enable(tz);
|
2020-07-27 23:10:33 +00:00
|
|
|
else
|
2024-01-03 11:59:10 +00:00
|
|
|
thermal_notify_tz_disable(tz);
|
2020-07-27 23:10:33 +00:00
|
|
|
|
2020-06-29 12:29:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int thermal_zone_device_enable(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_ENABLED);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_enable);
|
|
|
|
|
|
|
|
int thermal_zone_device_disable(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
|
|
|
|
|
|
|
|
int thermal_zone_device_is_enabled(struct thermal_zone_device *tz)
|
|
|
|
{
|
2022-08-05 15:38:34 +00:00
|
|
|
lockdep_assert_held(&tz->lock);
|
2020-06-29 12:29:20 +00:00
|
|
|
|
2022-08-05 15:38:34 +00:00
|
|
|
return tz->mode == THERMAL_DEVICE_ENABLED;
|
2020-06-29 12:29:20 +00:00
|
|
|
}
|
|
|
|
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
static bool thermal_zone_is_present(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
return !list_empty(&tz->node);
|
|
|
|
}
|
|
|
|
|
2016-08-26 23:21:16 +00:00
|
|
|
void thermal_zone_device_update(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_notify_event event)
|
2012-09-18 05:35:04 +00:00
|
|
|
{
|
2022-08-05 15:38:34 +00:00
|
|
|
mutex_lock(&tz->lock);
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
if (thermal_zone_is_present(tz))
|
2022-11-10 15:25:00 +00:00
|
|
|
__thermal_zone_device_update(tz, event);
|
2022-08-05 15:38:34 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_update);
|
2012-09-18 05:35:04 +00:00
|
|
|
|
2020-07-06 10:55:35 +00:00
|
|
|
int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct thermal_governor *gov;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
list_for_each_entry(gov, &thermal_governor_list, governor_list) {
|
|
|
|
ret = cb(gov, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
|
|
|
|
void *), void *data)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node) {
|
|
|
|
ret = cb(cdev, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
|
|
|
ret = cb(tz, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-07-06 10:55:36 +00:00
|
|
|
struct thermal_zone_device *thermal_zone_get_by_id(int id)
|
|
|
|
{
|
2020-07-24 17:01:05 +00:00
|
|
|
struct thermal_zone_device *tz, *match = NULL;
|
2020-07-06 10:55:36 +00:00
|
|
|
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
2020-07-24 17:01:05 +00:00
|
|
|
if (tz->id == id) {
|
|
|
|
match = tz;
|
2020-07-06 10:55:36 +00:00
|
|
|
break;
|
2020-07-24 17:01:05 +00:00
|
|
|
}
|
2020-07-06 10:55:36 +00:00
|
|
|
}
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2020-07-24 17:01:05 +00:00
|
|
|
return match;
|
2020-07-06 10:55:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 05:09:17 +00:00
|
|
|
/*
|
|
|
|
* Device management section: cooling devices, zones devices, and binding
|
|
|
|
*
|
|
|
|
* Set of functions provided by the thermal core for:
|
|
|
|
* - cooling devices lifecycle: registration, unregistration,
|
|
|
|
* binding, and unbinding.
|
|
|
|
* - thermal zone devices lifecycle: registration, unregistration,
|
|
|
|
* binding, and unbinding.
|
|
|
|
*/
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
/**
|
2023-09-21 18:01:43 +00:00
|
|
|
* thermal_bind_cdev_to_trip - bind a cooling device to a thermal zone
|
2013-04-23 21:48:16 +00:00
|
|
|
* @tz: pointer to struct thermal_zone_device
|
2023-09-21 18:01:43 +00:00
|
|
|
* @trip: trip point the cooling devices is associated with in this zone.
|
2013-04-23 21:48:16 +00: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 16:04:21 +00: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-07 21:55:08 +00:00
|
|
|
*
|
2013-04-23 21:48:16 +00:00
|
|
|
* This interface function bind a thermal cooling device to the certain trip
|
|
|
|
* point of a thermal zone device.
|
2008-02-07 21:55:08 +00:00
|
|
|
* This function is usually called in the thermal zone device .bind callback.
|
2013-04-23 21:48:16 +00:00
|
|
|
*
|
|
|
|
* Return: 0 on success, the proper error value otherwise.
|
2008-01-17 07:51:08 +00:00
|
|
|
*/
|
2023-09-21 18:01:43 +00:00
|
|
|
int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz,
|
|
|
|
const struct thermal_trip *trip,
|
2012-06-26 08:35:57 +00:00
|
|
|
struct thermal_cooling_device *cdev,
|
2015-02-18 16:04:21 +00:00
|
|
|
unsigned long upper, unsigned long lower,
|
|
|
|
unsigned int weight)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
2012-06-27 02:08:19 +00:00
|
|
|
struct thermal_instance *dev;
|
|
|
|
struct thermal_instance *pos;
|
2008-02-15 05:58:50 +00:00
|
|
|
struct thermal_zone_device *pos1;
|
|
|
|
struct thermal_cooling_device *pos2;
|
2023-03-17 17:01:26 +00:00
|
|
|
bool upper_no_limit;
|
2022-10-17 10:03:01 +00:00
|
|
|
int result;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2008-02-15 05:58:50 +00: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 07:51:08 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
2012-06-26 08:35:57 +00:00
|
|
|
/* lower default 0, upper default max_state */
|
|
|
|
lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
|
2023-03-17 17:01:26 +00:00
|
|
|
|
|
|
|
if (upper == THERMAL_NO_LIMIT) {
|
|
|
|
upper = cdev->max_state;
|
|
|
|
upper_no_limit = true;
|
|
|
|
} else {
|
|
|
|
upper_no_limit = false;
|
|
|
|
}
|
2012-06-26 08:35:57 +00:00
|
|
|
|
2022-10-17 10:03:01 +00:00
|
|
|
if (lower > upper || upper > cdev->max_state)
|
2012-06-26 08:35:57 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-11-08 05:09:25 +00:00
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
2008-01-17 07:51:08 +00:00
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
dev->tz = tz;
|
|
|
|
dev->cdev = cdev;
|
|
|
|
dev->trip = trip;
|
2012-06-26 08:35:57 +00:00
|
|
|
dev->upper = upper;
|
2023-03-17 17:01:26 +00:00
|
|
|
dev->upper_no_limit = upper_no_limit;
|
2012-06-26 08:35:57 +00:00
|
|
|
dev->lower = lower;
|
2012-06-27 06:13:04 +00:00
|
|
|
dev->target = THERMAL_NO_TARGET;
|
2015-02-18 16:04:21 +00:00
|
|
|
dev->weight = weight;
|
2012-06-26 08:27:22 +00:00
|
|
|
|
2022-05-27 07:34:45 +00:00
|
|
|
result = ida_alloc(&tz->ida, GFP_KERNEL);
|
2016-12-21 17:47:03 +00:00
|
|
|
if (result < 0)
|
2008-01-17 07:51:08 +00:00
|
|
|
goto free_mem;
|
|
|
|
|
2016-12-21 17:47:03 +00:00
|
|
|
dev->id = result;
|
2008-01-17 07:51:08 +00:00
|
|
|
sprintf(dev->name, "cdev%d", dev->id);
|
|
|
|
result =
|
|
|
|
sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
|
|
|
|
if (result)
|
2016-12-21 17:47:03 +00:00
|
|
|
goto release_ida;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-10-07 08:59:39 +00:00
|
|
|
snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point",
|
|
|
|
dev->id);
|
2010-04-06 21:34:51 +00:00
|
|
|
sysfs_attr_init(&dev->attr.attr);
|
2008-01-17 07:51:08 +00:00
|
|
|
dev->attr.attr.name = dev->attr_name;
|
|
|
|
dev->attr.attr.mode = 0444;
|
2018-04-03 09:49:03 +00:00
|
|
|
dev->attr.show = trip_point_show;
|
2008-01-17 07:51:08 +00:00
|
|
|
result = device_create_file(&tz->device, &dev->attr);
|
|
|
|
if (result)
|
|
|
|
goto remove_symbol_link;
|
|
|
|
|
2023-10-07 08:59:39 +00:00
|
|
|
snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),
|
|
|
|
"cdev%d_weight", dev->id);
|
2015-02-18 16:04:24 +00:00
|
|
|
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;
|
2018-04-03 09:49:03 +00:00
|
|
|
dev->weight_attr.show = weight_show;
|
|
|
|
dev->weight_attr.store = weight_store;
|
2015-02-18 16:04:24 +00:00
|
|
|
result = device_create_file(&tz->device, &dev->weight_attr);
|
|
|
|
if (result)
|
|
|
|
goto remove_trip_file;
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_lock(&tz->lock);
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_lock(&cdev->lock);
|
2012-06-27 02:09:36 +00:00
|
|
|
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
|
2016-11-08 05:09:23 +00:00
|
|
|
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
|
|
|
|
result = -EEXIST;
|
|
|
|
break;
|
|
|
|
}
|
2012-06-27 06:11:52 +00:00
|
|
|
if (!result) {
|
2012-06-27 02:09:36 +00:00
|
|
|
list_add_tail(&dev->tz_node, &tz->thermal_instances);
|
2012-06-27 06:11:52 +00:00
|
|
|
list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
|
2015-10-30 08:32:10 +00:00
|
|
|
atomic_set(&tz->need_update, 1);
|
2023-12-20 23:17:45 +00:00
|
|
|
|
|
|
|
thermal_governor_update_tz(tz, THERMAL_TZ_BIND_CDEV);
|
2012-06-27 06:11:52 +00:00
|
|
|
}
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
return 0;
|
|
|
|
|
2015-02-18 16:04:24 +00:00
|
|
|
device_remove_file(&tz->device, &dev->weight_attr);
|
|
|
|
remove_trip_file:
|
2008-01-17 07:51:08 +00:00
|
|
|
device_remove_file(&tz->device, &dev->attr);
|
2012-03-21 19:55:02 +00:00
|
|
|
remove_symbol_link:
|
2008-01-17 07:51:08 +00:00
|
|
|
sysfs_remove_link(&tz->device.kobj, dev->name);
|
2016-12-21 17:47:03 +00:00
|
|
|
release_ida:
|
2022-05-27 07:34:45 +00:00
|
|
|
ida_free(&tz->ida, dev->id);
|
2012-03-21 19:55:02 +00:00
|
|
|
free_mem:
|
2008-01-17 07:51:08 +00:00
|
|
|
kfree(dev);
|
|
|
|
return result;
|
|
|
|
}
|
2023-09-21 18:01:43 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_bind_cdev_to_trip);
|
|
|
|
|
|
|
|
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
|
|
|
|
int trip_index,
|
|
|
|
struct thermal_cooling_device *cdev,
|
|
|
|
unsigned long upper, unsigned long lower,
|
|
|
|
unsigned int weight)
|
|
|
|
{
|
|
|
|
if (trip_index < 0 || trip_index >= tz->num_trips)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2024-04-02 18:56:43 +00:00
|
|
|
return thermal_bind_cdev_to_trip(tz, &tz->trips[trip_index].trip, cdev,
|
2023-09-21 18:01:43 +00:00
|
|
|
upper, lower, weight);
|
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
/**
|
2023-09-21 18:01:43 +00:00
|
|
|
* thermal_unbind_cdev_from_trip - unbind a cooling device from a thermal zone.
|
2013-04-23 21:48:17 +00:00
|
|
|
* @tz: pointer to a struct thermal_zone_device.
|
2023-09-21 18:01:43 +00:00
|
|
|
* @trip: trip point the cooling devices is associated with in this zone.
|
2013-04-23 21:48:17 +00:00
|
|
|
* @cdev: pointer to a struct thermal_cooling_device.
|
2008-02-07 21:55:08 +00:00
|
|
|
*
|
2013-04-23 21:48:17 +00:00
|
|
|
* This interface function unbind a thermal cooling device from the certain
|
|
|
|
* trip point of a thermal zone device.
|
2008-02-07 21:55:08 +00:00
|
|
|
* This function is usually called in the thermal zone device .unbind callback.
|
2013-04-23 21:48:17 +00:00
|
|
|
*
|
|
|
|
* Return: 0 on success, the proper error value otherwise.
|
2008-01-17 07:51:08 +00:00
|
|
|
*/
|
2023-09-21 18:01:43 +00:00
|
|
|
int thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz,
|
|
|
|
const struct thermal_trip *trip,
|
|
|
|
struct thermal_cooling_device *cdev)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
2012-06-27 02:08:19 +00:00
|
|
|
struct thermal_instance *pos, *next;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_lock(&cdev->lock);
|
2012-06-27 02:09:36 +00:00
|
|
|
list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
|
2008-02-07 21:55:08 +00:00
|
|
|
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
|
2012-06-27 02:09:36 +00:00
|
|
|
list_del(&pos->tz_node);
|
2012-06-27 06:11:52 +00:00
|
|
|
list_del(&pos->cdev_node);
|
2023-12-20 23:17:45 +00:00
|
|
|
|
|
|
|
thermal_governor_update_tz(tz, THERMAL_TZ_UNBIND_CDEV);
|
|
|
|
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
goto unbind;
|
|
|
|
}
|
|
|
|
}
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
2012-03-21 19:55:02 +00:00
|
|
|
unbind:
|
2015-07-23 09:02:32 +00:00
|
|
|
device_remove_file(&tz->device, &pos->weight_attr);
|
2008-01-17 07:51:08 +00:00
|
|
|
device_remove_file(&tz->device, &pos->attr);
|
|
|
|
sysfs_remove_link(&tz->device.kobj, pos->name);
|
2022-05-27 07:34:45 +00:00
|
|
|
ida_free(&tz->ida, pos->id);
|
2008-01-17 07:51:08 +00:00
|
|
|
kfree(pos);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-09-21 18:01:43 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_unbind_cdev_from_trip);
|
|
|
|
|
|
|
|
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
|
|
|
|
int trip_index,
|
|
|
|
struct thermal_cooling_device *cdev)
|
|
|
|
{
|
|
|
|
if (trip_index < 0 || trip_index >= tz->num_trips)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2024-04-02 18:56:43 +00:00
|
|
|
return thermal_unbind_cdev_from_trip(tz, &tz->trips[trip_index].trip, cdev);
|
2023-09-21 18:01:43 +00:00
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
static void thermal_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
struct thermal_cooling_device *cdev;
|
|
|
|
|
2012-03-21 19:55:02 +00:00
|
|
|
if (!strncmp(dev_name(dev), "thermal_zone",
|
|
|
|
sizeof("thermal_zone") - 1)) {
|
2008-01-17 07:51:08 +00:00
|
|
|
tz = to_thermal_zone(dev);
|
2017-08-08 14:39:53 +00:00
|
|
|
thermal_zone_destroy_device_groups(tz);
|
2022-11-10 15:24:52 +00:00
|
|
|
mutex_destroy(&tz->lock);
|
2023-12-08 19:13:44 +00:00
|
|
|
complete(&tz->removal);
|
2016-11-08 05:09:23 +00:00
|
|
|
} else if (!strncmp(dev_name(dev), "cooling_device",
|
|
|
|
sizeof("cooling_device") - 1)) {
|
2008-01-17 07:51:08 +00:00
|
|
|
cdev = to_cooling_device(dev);
|
2023-01-18 08:38:25 +00:00
|
|
|
thermal_cooling_device_destroy_sysfs(cdev);
|
2024-01-05 13:45:11 +00:00
|
|
|
kfree_const(cdev->type);
|
2023-01-18 08:38:25 +00:00
|
|
|
ida_free(&thermal_cdev_ida, cdev->id);
|
2008-01-17 07:51:08 +00:00
|
|
|
kfree(cdev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:44:03 +00:00
|
|
|
static struct class *thermal_class;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2016-11-08 05:09:13 +00:00
|
|
|
static inline
|
|
|
|
void print_bind_err_msg(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_cooling_device *cdev, int ret)
|
2016-11-08 05:09:12 +00:00
|
|
|
{
|
|
|
|
dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
|
|
|
|
tz->type, cdev->type, ret);
|
|
|
|
}
|
|
|
|
|
2016-11-08 05:09:09 +00:00
|
|
|
static void bind_cdev(struct thermal_cooling_device *cdev)
|
|
|
|
{
|
2023-03-30 10:45:26 +00:00
|
|
|
int ret;
|
2016-11-08 05:09:09 +00:00
|
|
|
struct thermal_zone_device *pos = NULL;
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node) {
|
2024-02-22 17:18:01 +00:00
|
|
|
if (pos->ops.bind) {
|
|
|
|
ret = pos->ops.bind(pos, cdev);
|
2016-11-08 05:09:09 +00:00
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(pos, cdev, ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
/**
|
2013-09-26 19:55:01 +00:00
|
|
|
* __thermal_cooling_device_register() - register a new thermal cooling device
|
|
|
|
* @np: a pointer to a device tree node.
|
2008-01-17 07:51:08 +00:00
|
|
|
* @type: the thermal cooling device type.
|
|
|
|
* @devdata: device private data.
|
|
|
|
* @ops: standard thermal cooling devices callbacks.
|
2013-04-23 21:48:18 +00: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 19:55:01 +00: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-23 21:48:18 +00: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 07:51:08 +00:00
|
|
|
*/
|
2013-09-26 19:55:01 +00:00
|
|
|
static struct thermal_cooling_device *
|
|
|
|
__thermal_cooling_device_register(struct device_node *np,
|
2019-04-18 16:36:39 +00:00
|
|
|
const char *type, void *devdata,
|
2013-09-26 19:55:01 +00:00
|
|
|
const struct thermal_cooling_device_ops *ops)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev;
|
2015-10-30 08:32:10 +00:00
|
|
|
struct thermal_zone_device *pos = NULL;
|
2024-04-25 12:24:20 +00:00
|
|
|
unsigned long current_state;
|
2021-10-15 02:45:04 +00:00
|
|
|
int id, ret;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
|
|
|
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
|
2008-02-07 21:55:08 +00:00
|
|
|
!ops->set_cur_state)
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-01-23 20:44:03 +00:00
|
|
|
if (!thermal_class)
|
|
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
|
2016-11-08 05:09:25 +00:00
|
|
|
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
|
2008-01-17 07:51:08 +00:00
|
|
|
if (!cdev)
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2022-05-27 07:34:45 +00:00
|
|
|
ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
|
2021-03-14 11:13:29 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto out_kfree_cdev;
|
|
|
|
cdev->id = ret;
|
2021-10-15 02:45:04 +00:00
|
|
|
id = ret;
|
2021-03-14 11:13:29 +00:00
|
|
|
|
2024-01-05 13:45:11 +00:00
|
|
|
cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL);
|
2021-03-14 11:13:29 +00:00
|
|
|
if (!cdev->type) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_ida_remove;
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
|
|
|
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_init(&cdev->lock);
|
2012-06-27 06:11:52 +00:00
|
|
|
INIT_LIST_HEAD(&cdev->thermal_instances);
|
2013-09-26 19:55:01 +00:00
|
|
|
cdev->np = np;
|
2008-01-17 07:51:08 +00:00
|
|
|
cdev->ops = ops;
|
2014-02-17 03:02:55 +00:00
|
|
|
cdev->updated = false;
|
2023-01-23 20:44:03 +00:00
|
|
|
cdev->device.class = thermal_class;
|
2008-01-17 07:51:08 +00:00
|
|
|
cdev->devdata = devdata;
|
2022-10-17 10:03:01 +00:00
|
|
|
|
2022-10-28 15:02:34 +00:00
|
|
|
ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
|
2023-01-18 08:38:25 +00:00
|
|
|
if (ret)
|
|
|
|
goto out_cdev_type;
|
2022-10-17 10:03:01 +00:00
|
|
|
|
2024-04-25 12:24:20 +00:00
|
|
|
ret = cdev->ops->get_cur_state(cdev, ¤t_state);
|
|
|
|
if (ret)
|
|
|
|
goto out_cdev_type;
|
|
|
|
|
2018-04-02 10:56:25 +00:00
|
|
|
thermal_cooling_device_setup_sysfs(cdev);
|
2023-01-18 08:38:24 +00:00
|
|
|
|
2022-11-15 09:19:45 +00:00
|
|
|
ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
|
2023-01-18 08:38:25 +00:00
|
|
|
if (ret)
|
|
|
|
goto out_cooling_dev;
|
2023-01-18 08:38:24 +00:00
|
|
|
|
2021-03-14 11:13:29 +00:00
|
|
|
ret = device_register(&cdev->device);
|
2023-01-18 08:38:25 +00:00
|
|
|
if (ret) {
|
|
|
|
/* thermal_release() handles rest of the cleanup */
|
|
|
|
put_device(&cdev->device);
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2024-04-25 12:24:20 +00:00
|
|
|
thermal_debug_cdev_add(cdev, current_state);
|
|
|
|
|
2012-09-18 05:34:59 +00:00
|
|
|
/* Add 'this' new cdev to the global cdev list */
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
2023-03-22 19:34:12 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
list_add(&cdev->node, &thermal_cdev_list);
|
|
|
|
|
2012-09-18 05:34:59 +00:00
|
|
|
/* Update binding information for 'this' new cdev */
|
|
|
|
bind_cdev(cdev);
|
|
|
|
|
2015-10-30 08:32:10 +00:00
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
|
|
|
if (atomic_cmpxchg(&pos->need_update, 1, 0))
|
2016-08-26 23:21:16 +00:00
|
|
|
thermal_zone_device_update(pos,
|
|
|
|
THERMAL_EVENT_UNSPECIFIED);
|
2023-03-22 19:34:12 +00:00
|
|
|
|
2015-10-30 08:32:10 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2012-09-18 05:34:59 +00:00
|
|
|
return cdev;
|
2021-03-14 11:13:29 +00:00
|
|
|
|
2023-01-18 08:38:25 +00:00
|
|
|
out_cooling_dev:
|
2022-05-11 02:06:05 +00:00
|
|
|
thermal_cooling_device_destroy_sysfs(cdev);
|
2023-01-18 08:38:25 +00:00
|
|
|
out_cdev_type:
|
2024-01-05 13:45:11 +00:00
|
|
|
kfree_const(cdev->type);
|
2021-03-14 11:13:29 +00:00
|
|
|
out_ida_remove:
|
2022-05-27 07:34:45 +00:00
|
|
|
ida_free(&thermal_cdev_ida, id);
|
2021-03-14 11:13:29 +00:00
|
|
|
out_kfree_cdev:
|
2021-03-19 20:22:57 +00:00
|
|
|
kfree(cdev);
|
2021-03-14 11:13:29 +00:00
|
|
|
return ERR_PTR(ret);
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
2013-09-26 19:55:01 +00: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 *
|
2019-04-18 16:36:39 +00:00
|
|
|
thermal_cooling_device_register(const char *type, void *devdata,
|
2013-09-26 19:55:01 +00:00
|
|
|
const struct thermal_cooling_device_ops *ops)
|
|
|
|
{
|
|
|
|
return __thermal_cooling_device_register(NULL, type, devdata, ops);
|
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2013-09-26 19:55:01 +00: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,
|
2019-04-18 16:36:39 +00:00
|
|
|
const char *type, void *devdata,
|
2013-09-26 19:55:01 +00:00
|
|
|
const struct thermal_cooling_device_ops *ops)
|
|
|
|
{
|
|
|
|
return __thermal_cooling_device_register(np, type, devdata, ops);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
|
|
|
|
|
2019-04-18 19:58:15 +00:00
|
|
|
static void thermal_cooling_device_release(struct device *dev, void *res)
|
|
|
|
{
|
|
|
|
thermal_cooling_device_unregister(
|
|
|
|
*(struct thermal_cooling_device **)res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* devm_thermal_of_cooling_device_register() - register an OF thermal cooling
|
|
|
|
* device
|
|
|
|
* @dev: a valid struct device pointer of a sensor 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 *
|
|
|
|
devm_thermal_of_cooling_device_register(struct device *dev,
|
|
|
|
struct device_node *np,
|
|
|
|
char *type, void *devdata,
|
|
|
|
const struct thermal_cooling_device_ops *ops)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device **ptr, *tcd;
|
|
|
|
|
|
|
|
ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!ptr)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
tcd = __thermal_cooling_device_register(np, type, devdata, ops);
|
|
|
|
if (IS_ERR(tcd)) {
|
|
|
|
devres_free(ptr);
|
|
|
|
return tcd;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = tcd;
|
|
|
|
devres_add(dev, ptr);
|
|
|
|
|
|
|
|
return tcd;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
|
|
|
|
|
2023-03-17 16:54:34 +00:00
|
|
|
static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *pos = NULL;
|
|
|
|
|
|
|
|
list_for_each_entry(pos, &thermal_cdev_list, node) {
|
|
|
|
if (pos == cdev)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-17 17:01:26 +00:00
|
|
|
/**
|
|
|
|
* thermal_cooling_device_update - Update a cooling device object
|
|
|
|
* @cdev: Target cooling device.
|
|
|
|
*
|
|
|
|
* Update @cdev to reflect a change of the underlying hardware or platform.
|
|
|
|
*
|
|
|
|
* Must be called when the maximum cooling state of @cdev becomes invalid and so
|
|
|
|
* its .get_max_state() callback needs to be run to produce the new maximum
|
|
|
|
* cooling state value.
|
|
|
|
*/
|
|
|
|
void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
|
|
|
|
{
|
|
|
|
struct thermal_instance *ti;
|
|
|
|
unsigned long state;
|
|
|
|
|
|
|
|
if (IS_ERR_OR_NULL(cdev))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hold thermal_list_lock throughout the update to prevent the device
|
|
|
|
* from going away while being updated.
|
|
|
|
*/
|
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
if (!thermal_cooling_device_present(cdev))
|
|
|
|
goto unlock_list;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update under the cdev lock to prevent the state from being set beyond
|
|
|
|
* the new limit concurrently.
|
|
|
|
*/
|
|
|
|
mutex_lock(&cdev->lock);
|
|
|
|
|
|
|
|
if (cdev->ops->get_max_state(cdev, &cdev->max_state))
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
thermal_cooling_device_stats_reinit(cdev);
|
|
|
|
|
|
|
|
list_for_each_entry(ti, &cdev->thermal_instances, cdev_node) {
|
|
|
|
if (ti->upper == cdev->max_state)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (ti->upper < cdev->max_state) {
|
|
|
|
if (ti->upper_no_limit)
|
|
|
|
ti->upper = cdev->max_state;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ti->upper = cdev->max_state;
|
|
|
|
if (ti->lower > ti->upper)
|
|
|
|
ti->lower = ti->upper;
|
|
|
|
|
|
|
|
if (ti->target == THERMAL_NO_TARGET)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (ti->target > ti->upper)
|
|
|
|
ti->target = ti->upper;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cdev->ops->get_cur_state(cdev, &state) || state > cdev->max_state)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
thermal_cooling_device_stats_update(cdev, state);
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&cdev->lock);
|
|
|
|
|
|
|
|
unlock_list:
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
/**
|
2016-11-08 05:09:24 +00:00
|
|
|
* thermal_cooling_device_unregister - removes a thermal cooling device
|
2008-01-17 07:51:08 +00:00
|
|
|
* @cdev: the thermal cooling device to remove.
|
|
|
|
*
|
2016-11-08 05:09:24 +00:00
|
|
|
* thermal_cooling_device_unregister() must be called when a registered
|
|
|
|
* thermal cooling device is no longer needed.
|
2008-01-17 07:51:08 +00:00
|
|
|
*/
|
2012-09-18 05:34:59 +00:00
|
|
|
void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
|
|
|
|
if (!cdev)
|
|
|
|
return;
|
|
|
|
|
2024-01-09 09:41:11 +00:00
|
|
|
thermal_debug_cdev_remove(cdev);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
2023-03-17 16:54:34 +00:00
|
|
|
|
|
|
|
if (!thermal_cooling_device_present(cdev)) {
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
return;
|
|
|
|
}
|
2023-03-17 16:54:34 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
list_del(&cdev->node);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
|
|
|
/* Unbind all thermal zones associated with 'this' cdev */
|
2008-01-17 07:51:08 +00:00
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
2024-02-22 17:18:01 +00:00
|
|
|
if (tz->ops.unbind)
|
|
|
|
tz->ops.unbind(tz, cdev);
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2023-01-18 08:38:26 +00:00
|
|
|
device_unregister(&cdev->device);
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2016-11-08 05:09:10 +00:00
|
|
|
static void bind_tz(struct thermal_zone_device *tz)
|
2012-06-27 06:13:04 +00:00
|
|
|
{
|
2023-03-30 10:45:26 +00:00
|
|
|
int ret;
|
2016-11-08 05:09:10 +00:00
|
|
|
struct thermal_cooling_device *pos = NULL;
|
2012-06-27 06:13:04 +00:00
|
|
|
|
2024-02-22 17:18:01 +00:00
|
|
|
if (!tz->ops.bind)
|
2012-06-27 06:13:04 +00:00
|
|
|
return;
|
2012-07-25 02:10:58 +00:00
|
|
|
|
2016-11-08 05:09:10 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
2012-07-25 02:10:58 +00:00
|
|
|
|
2016-11-08 05:09:10 +00:00
|
|
|
list_for_each_entry(pos, &thermal_cdev_list, node) {
|
2024-02-22 17:18:01 +00:00
|
|
|
ret = tz->ops.bind(tz, pos);
|
2023-03-30 10:45:26 +00:00
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(tz, pos, ret);
|
2012-07-25 02:10:58 +00:00
|
|
|
}
|
2023-03-30 10:45:26 +00:00
|
|
|
|
2016-11-08 05:09:10 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
2012-07-25 02:10:58 +00:00
|
|
|
}
|
|
|
|
|
2022-07-22 20:00:03 +00:00
|
|
|
static void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
|
|
|
|
{
|
|
|
|
*delay_jiffies = msecs_to_jiffies(delay_ms);
|
|
|
|
if (delay_ms > 1000)
|
|
|
|
*delay_jiffies = round_jiffies(*delay_jiffies);
|
|
|
|
}
|
|
|
|
|
thermal/core: Add a generic thermal_zone_get_trip() function
The thermal_zone_device_ops structure defines a set of ops family,
get_trip_temp(), get_trip_hyst(), get_trip_type(). Each of them is
returning a property of a trip point.
The result is the code is calling the ops everywhere to get a trip
point which is supposed to be defined in the backend driver. It is a
non-sense as a thermal trip can be generic and used by the backend
driver to declare its trip points.
Part of the thermal framework has been changed and all the OF thermal
drivers are using the same definition for the trip point and use a
thermal zone registration variant to pass those trip points which are
part of the thermal zone device structure.
Consequently, we can use a generic function to get the trip points
when they are stored in the thermal zone device structure.
This approach can be generalized to all the drivers and we can get rid
of the ops->get_trip_*. That will result to a much more simpler code
and make possible to rework how the thermal trip are handled in the
thermal core framework as discussed previously.
This change adds a function thermal_zone_get_trip() where we get the
thermal trip point structure which contains all the properties (type,
temp, hyst) instead of doing multiple calls to ops->get_trip_*.
That opens the door for trip point extension with more attributes. For
instance, replacing the trip points disabled bitmask with a 'disabled'
field in the structure.
Here we replace all the calls to ops->get_trip_* in the thermal core
code with a call to the thermal_zone_get_trip() function.
The thermal zone ops defines a callback to retrieve the critical
temperature. As the trip handling is being reworked, all the trip
points will be the same whatever the driver and consequently finding
the critical trip temperature will be just a loop to search for a
critical trip point type.
Provide such a generic function, so we encapsulate the ops
get_crit_temp() which can be removed when all the backend drivers are
using the generic trip points handling.
While at it, add the thermal_zone_get_num_trips() to encapsulate the
code more and reduce the grip with the thermal framework internals.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Link: https://lore.kernel.org/r/20221003092602.1323944-2-daniel.lezcano@linaro.org
2022-10-03 09:25:34 +00:00
|
|
|
int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
|
|
|
|
{
|
2024-04-02 18:56:43 +00:00
|
|
|
const struct thermal_trip_desc *td;
|
|
|
|
int ret = -EINVAL;
|
thermal/core: Add a generic thermal_zone_get_trip() function
The thermal_zone_device_ops structure defines a set of ops family,
get_trip_temp(), get_trip_hyst(), get_trip_type(). Each of them is
returning a property of a trip point.
The result is the code is calling the ops everywhere to get a trip
point which is supposed to be defined in the backend driver. It is a
non-sense as a thermal trip can be generic and used by the backend
driver to declare its trip points.
Part of the thermal framework has been changed and all the OF thermal
drivers are using the same definition for the trip point and use a
thermal zone registration variant to pass those trip points which are
part of the thermal zone device structure.
Consequently, we can use a generic function to get the trip points
when they are stored in the thermal zone device structure.
This approach can be generalized to all the drivers and we can get rid
of the ops->get_trip_*. That will result to a much more simpler code
and make possible to rework how the thermal trip are handled in the
thermal core framework as discussed previously.
This change adds a function thermal_zone_get_trip() where we get the
thermal trip point structure which contains all the properties (type,
temp, hyst) instead of doing multiple calls to ops->get_trip_*.
That opens the door for trip point extension with more attributes. For
instance, replacing the trip points disabled bitmask with a 'disabled'
field in the structure.
Here we replace all the calls to ops->get_trip_* in the thermal core
code with a call to the thermal_zone_get_trip() function.
The thermal zone ops defines a callback to retrieve the critical
temperature. As the trip handling is being reworked, all the trip
points will be the same whatever the driver and consequently finding
the critical trip temperature will be just a loop to search for a
critical trip point type.
Provide such a generic function, so we encapsulate the ops
get_crit_temp() which can be removed when all the backend drivers are
using the generic trip points handling.
While at it, add the thermal_zone_get_num_trips() to encapsulate the
code more and reduce the grip with the thermal framework internals.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Link: https://lore.kernel.org/r/20221003092602.1323944-2-daniel.lezcano@linaro.org
2022-10-03 09:25:34 +00:00
|
|
|
|
2024-02-22 17:18:01 +00:00
|
|
|
if (tz->ops.get_crit_temp)
|
|
|
|
return tz->ops.get_crit_temp(tz, temp);
|
thermal/core: Add a generic thermal_zone_get_trip() function
The thermal_zone_device_ops structure defines a set of ops family,
get_trip_temp(), get_trip_hyst(), get_trip_type(). Each of them is
returning a property of a trip point.
The result is the code is calling the ops everywhere to get a trip
point which is supposed to be defined in the backend driver. It is a
non-sense as a thermal trip can be generic and used by the backend
driver to declare its trip points.
Part of the thermal framework has been changed and all the OF thermal
drivers are using the same definition for the trip point and use a
thermal zone registration variant to pass those trip points which are
part of the thermal zone device structure.
Consequently, we can use a generic function to get the trip points
when they are stored in the thermal zone device structure.
This approach can be generalized to all the drivers and we can get rid
of the ops->get_trip_*. That will result to a much more simpler code
and make possible to rework how the thermal trip are handled in the
thermal core framework as discussed previously.
This change adds a function thermal_zone_get_trip() where we get the
thermal trip point structure which contains all the properties (type,
temp, hyst) instead of doing multiple calls to ops->get_trip_*.
That opens the door for trip point extension with more attributes. For
instance, replacing the trip points disabled bitmask with a 'disabled'
field in the structure.
Here we replace all the calls to ops->get_trip_* in the thermal core
code with a call to the thermal_zone_get_trip() function.
The thermal zone ops defines a callback to retrieve the critical
temperature. As the trip handling is being reworked, all the trip
points will be the same whatever the driver and consequently finding
the critical trip temperature will be just a loop to search for a
critical trip point type.
Provide such a generic function, so we encapsulate the ops
get_crit_temp() which can be removed when all the backend drivers are
using the generic trip points handling.
While at it, add the thermal_zone_get_num_trips() to encapsulate the
code more and reduce the grip with the thermal framework internals.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Link: https://lore.kernel.org/r/20221003092602.1323944-2-daniel.lezcano@linaro.org
2022-10-03 09:25:34 +00:00
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
2024-04-02 18:56:43 +00:00
|
|
|
for_each_trip_desc(tz, td) {
|
|
|
|
const struct thermal_trip *trip = &td->trip;
|
|
|
|
|
|
|
|
if (trip->type == THERMAL_TRIP_CRITICAL) {
|
|
|
|
*temp = trip->temperature;
|
thermal/core: Add a generic thermal_zone_get_trip() function
The thermal_zone_device_ops structure defines a set of ops family,
get_trip_temp(), get_trip_hyst(), get_trip_type(). Each of them is
returning a property of a trip point.
The result is the code is calling the ops everywhere to get a trip
point which is supposed to be defined in the backend driver. It is a
non-sense as a thermal trip can be generic and used by the backend
driver to declare its trip points.
Part of the thermal framework has been changed and all the OF thermal
drivers are using the same definition for the trip point and use a
thermal zone registration variant to pass those trip points which are
part of the thermal zone device structure.
Consequently, we can use a generic function to get the trip points
when they are stored in the thermal zone device structure.
This approach can be generalized to all the drivers and we can get rid
of the ops->get_trip_*. That will result to a much more simpler code
and make possible to rework how the thermal trip are handled in the
thermal core framework as discussed previously.
This change adds a function thermal_zone_get_trip() where we get the
thermal trip point structure which contains all the properties (type,
temp, hyst) instead of doing multiple calls to ops->get_trip_*.
That opens the door for trip point extension with more attributes. For
instance, replacing the trip points disabled bitmask with a 'disabled'
field in the structure.
Here we replace all the calls to ops->get_trip_* in the thermal core
code with a call to the thermal_zone_get_trip() function.
The thermal zone ops defines a callback to retrieve the critical
temperature. As the trip handling is being reworked, all the trip
points will be the same whatever the driver and consequently finding
the critical trip temperature will be just a loop to search for a
critical trip point type.
Provide such a generic function, so we encapsulate the ops
get_crit_temp() which can be removed when all the backend drivers are
using the generic trip points handling.
While at it, add the thermal_zone_get_num_trips() to encapsulate the
code more and reduce the grip with the thermal framework internals.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Link: https://lore.kernel.org/r/20221003092602.1323944-2-daniel.lezcano@linaro.org
2022-10-03 09:25:34 +00:00
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
/**
|
2022-07-22 20:00:05 +00:00
|
|
|
* thermal_zone_device_register_with_trips() - register a new thermal zone device
|
2008-01-17 07:51:08 +00:00
|
|
|
* @type: the thermal zone device type
|
2022-07-22 20:00:05 +00:00
|
|
|
* @trips: a pointer to an array of thermal trips
|
2022-07-22 20:00:04 +00:00
|
|
|
* @num_trips: the number of trip points the thermal zone support
|
2008-01-17 07:51:08 +00:00
|
|
|
* @devdata: private device data
|
|
|
|
* @ops: standard thermal zone device callbacks
|
2012-09-18 05:34:56 +00:00
|
|
|
* @tzp: thermal zone platform parameters
|
2008-12-03 17:55:32 +00: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 07:51:08 +00:00
|
|
|
*
|
2013-04-23 21:48:20 +00: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 07:51:08 +00:00
|
|
|
* thermal_zone_device_unregister() must be called when the device is no
|
2012-06-27 01:51:12 +00:00
|
|
|
* longer needed. The passive cooling depends on the .get_trend() return value.
|
2013-04-23 21:48:20 +00: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 07:51:08 +00:00
|
|
|
*/
|
2016-11-08 05:09:21 +00:00
|
|
|
struct thermal_zone_device *
|
2024-02-22 13:52:01 +00:00
|
|
|
thermal_zone_device_register_with_trips(const char *type,
|
|
|
|
const struct thermal_trip *trips,
|
2024-02-22 18:09:16 +00:00
|
|
|
int num_trips, void *devdata,
|
2024-02-22 17:18:01 +00:00
|
|
|
const struct thermal_zone_device_ops *ops,
|
2024-02-22 13:52:01 +00:00
|
|
|
const struct thermal_zone_params *tzp,
|
|
|
|
int passive_delay, int polling_delay)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
2024-04-02 18:56:43 +00:00
|
|
|
const struct thermal_trip *trip = trips;
|
2008-01-17 07:51:08 +00:00
|
|
|
struct thermal_zone_device *tz;
|
2024-04-02 18:56:43 +00:00
|
|
|
struct thermal_trip_desc *td;
|
2019-08-07 03:01:30 +00:00
|
|
|
int id;
|
2008-01-17 07:51:08 +00:00
|
|
|
int result;
|
2015-02-26 19:00:27 +00:00
|
|
|
struct thermal_governor *governor;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2019-07-11 20:31:58 +00:00
|
|
|
if (!type || strlen(type) == 0) {
|
2022-07-22 19:59:58 +00:00
|
|
|
pr_err("No thermal zone type defined\n");
|
2016-11-08 05:08:39 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
2019-07-11 20:31:58 +00:00
|
|
|
}
|
2016-11-08 05:08:39 +00:00
|
|
|
|
2022-09-09 18:13:22 +00:00
|
|
|
if (strlen(type) >= THERMAL_NAME_LENGTH) {
|
2022-07-22 19:59:58 +00:00
|
|
|
pr_err("Thermal zone name (%s) too long, should be under %d chars\n",
|
2019-07-11 20:31:58 +00:00
|
|
|
type, THERMAL_NAME_LENGTH);
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
2019-07-11 20:31:58 +00:00
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2024-02-22 18:09:16 +00:00
|
|
|
if (num_trips < 0) {
|
2022-07-22 19:59:58 +00:00
|
|
|
pr_err("Incorrect number of thermal trips\n");
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
2019-07-11 20:31:58 +00:00
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-12-13 12:13:22 +00:00
|
|
|
if (!ops || !ops->get_temp) {
|
2022-07-22 19:59:58 +00:00
|
|
|
pr_err("Thermal zone device ops not defined\n");
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
2019-07-11 20:31:58 +00:00
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-08-29 18:46:31 +00:00
|
|
|
if (num_trips > 0 && !trips)
|
2013-01-02 15:29:42 +00:00
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
2023-01-23 20:44:03 +00:00
|
|
|
if (!thermal_class)
|
|
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
|
2024-02-22 13:52:01 +00:00
|
|
|
tz = kzalloc(struct_size(tz, trips, num_trips), GFP_KERNEL);
|
2008-01-17 07:51:08 +00:00
|
|
|
if (!tz)
|
2008-02-15 05:59:50 +00:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-04-04 07:51:37 +00:00
|
|
|
if (tzp) {
|
|
|
|
tz->tzp = kmemdup(tzp, sizeof(*tzp), GFP_KERNEL);
|
|
|
|
if (!tz->tzp) {
|
|
|
|
result = -ENOMEM;
|
|
|
|
goto free_tz;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-27 02:09:00 +00:00
|
|
|
INIT_LIST_HEAD(&tz->thermal_instances);
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
INIT_LIST_HEAD(&tz->node);
|
2016-12-21 17:47:03 +00:00
|
|
|
ida_init(&tz->ida);
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_init(&tz->lock);
|
2023-12-08 19:13:44 +00:00
|
|
|
init_completion(&tz->removal);
|
2022-05-27 07:34:45 +00:00
|
|
|
id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);
|
2019-08-07 03:01:30 +00:00
|
|
|
if (id < 0) {
|
|
|
|
result = id;
|
2023-04-04 07:51:37 +00:00
|
|
|
goto free_tzp;
|
2019-08-07 03:01:30 +00:00
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2019-08-07 03:01:30 +00:00
|
|
|
tz->id = id;
|
2022-08-18 21:01:11 +00:00
|
|
|
strscpy(tz->type, type, sizeof(tz->type));
|
2020-12-10 12:15:11 +00:00
|
|
|
|
2024-02-22 17:18:01 +00:00
|
|
|
tz->ops = *ops;
|
|
|
|
if (!tz->ops.critical)
|
|
|
|
tz->ops.critical = thermal_zone_device_critical;
|
2020-12-10 12:15:11 +00:00
|
|
|
|
2023-01-23 20:44:03 +00:00
|
|
|
tz->device.class = thermal_class;
|
2008-01-17 07:51:08 +00:00
|
|
|
tz->devdata = devdata;
|
2022-07-22 20:00:04 +00:00
|
|
|
tz->num_trips = num_trips;
|
2024-05-17 09:24:03 +00:00
|
|
|
for_each_trip_desc(tz, td) {
|
2024-04-02 18:56:43 +00:00
|
|
|
td->trip = *trip++;
|
2024-05-17 09:24:03 +00:00
|
|
|
/*
|
|
|
|
* Mark all thresholds as invalid to start with even though
|
|
|
|
* this only matters for the trips that start as invalid and
|
|
|
|
* become valid later.
|
|
|
|
*/
|
|
|
|
td->threshold = INT_MAX;
|
|
|
|
}
|
2016-11-08 05:08:42 +00:00
|
|
|
|
2020-12-16 22:03:35 +00:00
|
|
|
thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay);
|
|
|
|
thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay);
|
|
|
|
|
2016-11-08 05:08:52 +00:00
|
|
|
/* sys I/F */
|
2016-11-08 05:08:42 +00:00
|
|
|
/* Add nodes that are always present via .groups */
|
2024-02-22 17:30:49 +00:00
|
|
|
result = thermal_zone_create_device_groups(tz);
|
2016-11-08 05:08:52 +00:00
|
|
|
if (result)
|
2017-08-08 14:39:54 +00:00
|
|
|
goto remove_id;
|
2016-11-08 05:08:52 +00:00
|
|
|
|
2015-10-30 08:32:10 +00:00
|
|
|
/* A new thermal zone needs to be updated anyway. */
|
|
|
|
atomic_set(&tz->need_update, 1);
|
2008-12-03 17:55:32 +00:00
|
|
|
|
2022-11-15 09:19:45 +00:00
|
|
|
result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
|
|
|
|
if (result) {
|
|
|
|
thermal_zone_destroy_device_groups(tz);
|
|
|
|
goto remove_id;
|
|
|
|
}
|
2008-01-17 07:51:08 +00:00
|
|
|
result = device_register(&tz->device);
|
2017-08-08 14:39:54 +00:00
|
|
|
if (result)
|
2019-08-07 03:01:30 +00:00
|
|
|
goto release_device;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2012-09-18 05:34:57 +00:00
|
|
|
/* Update 'this' zone's governor information */
|
|
|
|
mutex_lock(&thermal_governor_lock);
|
|
|
|
|
|
|
|
if (tz->tzp)
|
2015-02-26 19:00:27 +00:00
|
|
|
governor = __find_governor(tz->tzp->governor_name);
|
2012-09-18 05:34:57 +00:00
|
|
|
else
|
2015-02-26 19:00:27 +00:00
|
|
|
governor = def_governor;
|
|
|
|
|
|
|
|
result = thermal_set_governor(tz, governor);
|
|
|
|
if (result) {
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
goto unregister;
|
|
|
|
}
|
2012-09-18 05:34:57 +00:00
|
|
|
|
|
|
|
mutex_unlock(&thermal_governor_lock);
|
|
|
|
|
2013-08-15 15:34:17 +00:00
|
|
|
if (!tz->tzp || !tz->tzp->no_hwmon) {
|
|
|
|
result = thermal_add_hwmon_sysfs(tz);
|
|
|
|
if (result)
|
|
|
|
goto unregister;
|
|
|
|
}
|
2008-04-21 08:07:52 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
mutex_lock(&tz->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
list_add_tail(&tz->node, &thermal_tz_list);
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2012-09-18 05:34:59 +00:00
|
|
|
/* Bind cooling devices for this zone */
|
|
|
|
bind_tz(tz);
|
|
|
|
|
2020-12-22 18:11:10 +00:00
|
|
|
thermal_zone_device_init(tz);
|
2015-10-30 08:32:10 +00:00
|
|
|
/* Update the new thermal zone and mark it as already updated. */
|
|
|
|
if (atomic_cmpxchg(&tz->need_update, 1, 0))
|
2016-08-26 23:21:16 +00:00
|
|
|
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
2008-12-03 17:55:32 +00:00
|
|
|
|
2024-01-03 11:59:10 +00:00
|
|
|
thermal_notify_tz_create(tz);
|
2020-07-06 10:55:38 +00:00
|
|
|
|
thermal/debugfs: Add thermal debugfs information for mitigation episodes
The mitigation episodes are recorded. A mitigation episode happens
when the first trip point is crossed the way up and then the way
down. During this episode other trip points can be crossed also and
are accounted for this mitigation episode. The interesting information
is the average temperature at the trip point, the undershot and the
overshot. The standard deviation of the mitigated temperature will be
added later.
The thermal debugfs directory structure tries to stay consistent with
the sysfs one but in a very simplified way:
thermal/
`-- thermal_zones
|-- 0
| `-- mitigations
`-- 1
`-- mitigations
The content of the mitigations file has the following format:
,-Mitigation at 349988258us, duration=130136ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 130136 | 68227 | 62500 | 75625 |
| 1 | passive | 75000 | 2000 | 104209 | 74857 | 71666 | 77500 |
,-Mitigation at 272451637us, duration=75000ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 75000 | 68561 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 60714 | 74820 | 70555 | 77500 |
,-Mitigation at 238184119us, duration=27316ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 27316 | 73377 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 19468 | 75284 | 69444 | 77500 |
,-Mitigation at 39863713us, duration=136196ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 136196 | 73922 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 91721 | 74386 | 69444 | 78125 |
More information for a better understanding of the thermal behavior
will be added after. The idea is to give detailed statistics
information about the undershots and overshots, the temperature speed,
etc... As all the information in a single file is too much, the idea
would be to create a directory named with the mitigation timestamp
where all data could be added.
Please note this code is immune against trip ordering but not against
a trip temperature change while a mitigation is happening. However,
this situation should be extremely rare, perhaps not happening and we
might question ourselves if something should be done in the core
framework for other components first.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
[ rjw: White space fixups, rebase ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-01-09 09:41:12 +00:00
|
|
|
thermal_debug_tz_add(tz);
|
|
|
|
|
2014-10-28 07:40:25 +00:00
|
|
|
return tz;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2012-03-21 19:55:02 +00:00
|
|
|
unregister:
|
2019-08-07 03:01:30 +00:00
|
|
|
device_del(&tz->device);
|
|
|
|
release_device:
|
|
|
|
put_device(&tz->device);
|
2017-08-08 14:39:54 +00:00
|
|
|
remove_id:
|
2022-05-27 07:34:45 +00:00
|
|
|
ida_free(&thermal_tz_ida, id);
|
2023-04-04 07:51:37 +00:00
|
|
|
free_tzp:
|
|
|
|
kfree(tz->tzp);
|
2017-08-08 14:39:54 +00:00
|
|
|
free_tz:
|
|
|
|
kfree(tz);
|
|
|
|
return ERR_PTR(result);
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
2022-08-10 10:07:31 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
|
2022-07-22 20:00:05 +00:00
|
|
|
|
2023-08-30 16:13:35 +00:00
|
|
|
struct thermal_zone_device *thermal_tripless_zone_device_register(
|
|
|
|
const char *type,
|
|
|
|
void *devdata,
|
2024-02-22 17:18:01 +00:00
|
|
|
const struct thermal_zone_device_ops *ops,
|
2023-08-30 16:13:35 +00:00
|
|
|
const struct thermal_zone_params *tzp)
|
|
|
|
{
|
2024-02-22 18:09:16 +00:00
|
|
|
return thermal_zone_device_register_with_trips(type, NULL, 0, devdata,
|
2023-08-30 16:13:35 +00:00
|
|
|
ops, tzp, 0, 0);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_tripless_zone_device_register);
|
|
|
|
|
2023-03-01 20:14:29 +00:00
|
|
|
void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
|
|
|
|
{
|
|
|
|
return tzd->devdata;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_priv);
|
|
|
|
|
2023-03-01 20:14:38 +00:00
|
|
|
const char *thermal_zone_device_type(struct thermal_zone_device *tzd)
|
|
|
|
{
|
|
|
|
return tzd->type;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_type);
|
|
|
|
|
2023-03-01 20:14:40 +00:00
|
|
|
int thermal_zone_device_id(struct thermal_zone_device *tzd)
|
|
|
|
{
|
|
|
|
return tzd->id;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_id);
|
|
|
|
|
2023-04-19 08:33:38 +00:00
|
|
|
struct device *thermal_zone_device(struct thermal_zone_device *tzd)
|
|
|
|
{
|
|
|
|
return &tzd->device;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
/**
|
2021-05-17 05:10:20 +00:00
|
|
|
* thermal_zone_device_unregister - removes the registered thermal zone device
|
2008-01-17 07:51:08 +00:00
|
|
|
* @tz: the thermal zone device to remove
|
|
|
|
*/
|
|
|
|
void thermal_zone_device_unregister(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
struct thermal_cooling_device *cdev;
|
|
|
|
struct thermal_zone_device *pos = NULL;
|
|
|
|
|
|
|
|
if (!tz)
|
|
|
|
return;
|
|
|
|
|
thermal/debugfs: Add thermal debugfs information for mitigation episodes
The mitigation episodes are recorded. A mitigation episode happens
when the first trip point is crossed the way up and then the way
down. During this episode other trip points can be crossed also and
are accounted for this mitigation episode. The interesting information
is the average temperature at the trip point, the undershot and the
overshot. The standard deviation of the mitigated temperature will be
added later.
The thermal debugfs directory structure tries to stay consistent with
the sysfs one but in a very simplified way:
thermal/
`-- thermal_zones
|-- 0
| `-- mitigations
`-- 1
`-- mitigations
The content of the mitigations file has the following format:
,-Mitigation at 349988258us, duration=130136ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 130136 | 68227 | 62500 | 75625 |
| 1 | passive | 75000 | 2000 | 104209 | 74857 | 71666 | 77500 |
,-Mitigation at 272451637us, duration=75000ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 75000 | 68561 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 60714 | 74820 | 70555 | 77500 |
,-Mitigation at 238184119us, duration=27316ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 27316 | 73377 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 19468 | 75284 | 69444 | 77500 |
,-Mitigation at 39863713us, duration=136196ms
| trip | type | temp(°mC) | hyst(°mC) | duration | avg(°mC) | min(°mC) | max(°mC) |
| 0 | passive | 65000 | 2000 | 136196 | 73922 | 62500 | 75000 |
| 1 | passive | 75000 | 2000 | 91721 | 74386 | 69444 | 78125 |
More information for a better understanding of the thermal behavior
will be added after. The idea is to give detailed statistics
information about the undershots and overshots, the temperature speed,
etc... As all the information in a single file is too much, the idea
would be to create a directory named with the mitigation timestamp
where all data could be added.
Please note this code is immune against trip ordering but not against
a trip temperature change while a mitigation is happening. However,
this situation should be extremely rare, perhaps not happening and we
might question ourselves if something should be done in the core
framework for other components first.
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
[ rjw: White space fixups, rebase ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-01-09 09:41:12 +00:00
|
|
|
thermal_debug_tz_remove(tz);
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
2016-11-08 05:09:23 +00:00
|
|
|
if (pos == tz)
|
|
|
|
break;
|
2008-01-17 07:51:08 +00:00
|
|
|
if (pos != tz) {
|
|
|
|
/* thermal zone device not found */
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
return;
|
|
|
|
}
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
list_del(&tz->node);
|
thermal: core: Rework thermal zone availability check
In order to avoid running __thermal_zone_device_update() for thermal
zones going away, the thermal zone lock is held around device_del()
in thermal_zone_device_unregister() and thermal_zone_device_update()
passes the given thermal zone device to device_is_registered().
This allows thermal_zone_device_update() to skip the
__thermal_zone_device_update() if device_del() has already run for
the thermal zone at hand.
However, instead of looking at driver core internals, the thermal
subsystem may as well rely on its own data structures for this
purpose. Namely, if the thermal zone is not present in
thermal_tz_list, it can be regarded as unavailable, which in fact is
already the case in thermal_zone_device_unregister(). Accordingly,
the device_is_registered() check in thermal_zone_device_update() can
be replaced with checking whether or not the node list_head in struct
thermal_zone_device is empty, in which case it is not there in
thermal_tz_list.
To make this work, though, it is necessary to initialize tz->node
in thermal_zone_device_register_with_trips() before registering the
thermal zone device and it needs to be added to thermal_tz_list and
deleted from it under its zone lock.
After the above modifications, the zone lock does not need to be
held around device_del() in thermal_zone_device_unregister() any more.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-and-tested-by: Lukasz Luba <lukasz.luba@arm.com>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
2023-12-08 19:20:00 +00:00
|
|
|
mutex_unlock(&tz->lock);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
|
|
|
/* Unbind all cdevs associated with 'this' thermal zone */
|
2023-03-30 10:45:26 +00:00
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node)
|
2024-02-22 17:18:01 +00:00
|
|
|
if (tz->ops.unbind)
|
|
|
|
tz->ops.unbind(tz, cdev);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
mutex_unlock(&thermal_list_lock);
|
|
|
|
|
2019-11-12 20:42:23 +00:00
|
|
|
cancel_delayed_work_sync(&tz->poll_queue);
|
2008-12-03 17:55:32 +00:00
|
|
|
|
2015-02-26 19:00:27 +00:00
|
|
|
thermal_set_governor(tz, NULL);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2008-04-21 08:07:52 +00:00
|
|
|
thermal_remove_hwmon_sysfs(tz);
|
2022-05-27 07:34:45 +00:00
|
|
|
ida_free(&thermal_tz_ida, tz->id);
|
2016-12-21 17:47:03 +00:00
|
|
|
ida_destroy(&tz->ida);
|
2022-11-10 15:24:53 +00:00
|
|
|
|
|
|
|
device_del(&tz->device);
|
|
|
|
|
2023-04-04 07:51:37 +00:00
|
|
|
kfree(tz->tzp);
|
|
|
|
|
2022-11-10 15:24:53 +00:00
|
|
|
put_device(&tz->device);
|
2020-07-06 10:55:38 +00:00
|
|
|
|
2024-01-03 11:59:10 +00:00
|
|
|
thermal_notify_tz_delete(tz);
|
2023-12-08 19:13:44 +00:00
|
|
|
|
|
|
|
wait_for_completion(&tz->removal);
|
|
|
|
kfree(tz);
|
2008-01-17 07:51:08 +00:00
|
|
|
}
|
2013-04-23 21:48:15 +00:00
|
|
|
EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2013-04-05 12:32:28 +00: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-13 22:55:01 +00:00
|
|
|
if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
|
2013-04-05 12:32:28 +00: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);
|
|
|
|
|
2023-12-18 19:28:31 +00:00
|
|
|
static void thermal_zone_device_resume(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
|
|
|
|
tz = container_of(work, struct thermal_zone_device, poll_queue.work);
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
tz->suspended = false;
|
|
|
|
|
|
|
|
thermal_zone_device_init(tz);
|
|
|
|
__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
2015-10-30 08:31:58 +00:00
|
|
|
static int thermal_pm_notify(struct notifier_block *nb,
|
2016-11-08 05:09:21 +00:00
|
|
|
unsigned long mode, void *_unused)
|
2015-10-30 08:31:58 +00:00
|
|
|
{
|
|
|
|
struct thermal_zone_device *tz;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case PM_HIBERNATION_PREPARE:
|
|
|
|
case PM_RESTORE_PREPARE:
|
|
|
|
case PM_SUSPEND_PREPARE:
|
2023-12-18 19:25:02 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
tz->suspended = true;
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
2015-10-30 08:31:58 +00:00
|
|
|
break;
|
|
|
|
case PM_POST_HIBERNATION:
|
|
|
|
case PM_POST_RESTORE:
|
|
|
|
case PM_POST_SUSPEND:
|
2023-12-18 19:25:02 +00:00
|
|
|
mutex_lock(&thermal_list_lock);
|
|
|
|
|
2015-10-30 08:31:58 +00:00
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node) {
|
2023-12-18 19:25:02 +00:00
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
2023-12-18 19:26:47 +00:00
|
|
|
cancel_delayed_work(&tz->poll_queue);
|
|
|
|
|
2023-12-18 19:28:31 +00:00
|
|
|
/*
|
|
|
|
* Replace the work function with the resume one, which
|
|
|
|
* will restore the original work function and schedule
|
|
|
|
* the polling work if needed.
|
|
|
|
*/
|
|
|
|
INIT_DELAYED_WORK(&tz->poll_queue,
|
|
|
|
thermal_zone_device_resume);
|
|
|
|
/* Queue up the work without a delay. */
|
|
|
|
mod_delayed_work(system_freezable_power_efficient_wq,
|
|
|
|
&tz->poll_queue, 0);
|
2023-12-18 19:25:02 +00:00
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
2015-10-30 08:31:58 +00:00
|
|
|
}
|
2023-12-18 19:25:02 +00:00
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
2015-10-30 08:31:58 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block thermal_pm_nb = {
|
|
|
|
.notifier_call = thermal_pm_notify,
|
|
|
|
};
|
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
static int __init thermal_init(void)
|
|
|
|
{
|
2013-03-26 08:38:29 +00:00
|
|
|
int result;
|
|
|
|
|
2024-01-09 09:41:11 +00:00
|
|
|
thermal_debug_init();
|
|
|
|
|
2020-07-17 16:42:16 +00:00
|
|
|
result = thermal_netlink_init();
|
|
|
|
if (result)
|
|
|
|
goto error;
|
|
|
|
|
2013-03-26 08:38:29 +00:00
|
|
|
result = thermal_register_governors();
|
|
|
|
if (result)
|
2023-01-23 15:27:54 +00:00
|
|
|
goto unregister_netlink;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2023-01-23 20:44:03 +00:00
|
|
|
thermal_class = kzalloc(sizeof(*thermal_class), GFP_KERNEL);
|
|
|
|
if (!thermal_class) {
|
|
|
|
result = -ENOMEM;
|
|
|
|
goto unregister_governors;
|
|
|
|
}
|
|
|
|
|
|
|
|
thermal_class->name = "thermal";
|
|
|
|
thermal_class->dev_release = thermal_release;
|
|
|
|
|
|
|
|
result = class_register(thermal_class);
|
|
|
|
if (result) {
|
|
|
|
kfree(thermal_class);
|
|
|
|
thermal_class = NULL;
|
2013-03-26 08:38:29 +00:00
|
|
|
goto unregister_governors;
|
2023-01-23 20:44:03 +00:00
|
|
|
}
|
2013-03-26 08:38:29 +00:00
|
|
|
|
2015-10-30 08:31:58 +00: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 08:38:29 +00:00
|
|
|
return 0;
|
|
|
|
|
2014-12-03 21:20:21 +00:00
|
|
|
unregister_governors:
|
|
|
|
thermal_unregister_governors();
|
2023-01-23 15:27:54 +00:00
|
|
|
unregister_netlink:
|
|
|
|
thermal_netlink_exit();
|
2013-03-26 08:38:29 +00:00
|
|
|
error:
|
|
|
|
mutex_destroy(&thermal_list_lock);
|
|
|
|
mutex_destroy(&thermal_governor_lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
return result;
|
|
|
|
}
|
2020-07-17 16:42:17 +00:00
|
|
|
postcore_initcall(thermal_init);
|