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
|
|
|
}
|
|
|
|
|
2024-07-18 19:00:35 +00:00
|
|
|
static int __thermal_zone_device_set_mode(struct thermal_zone_device *tz,
|
|
|
|
enum thermal_device_mode mode)
|
|
|
|
{
|
|
|
|
if (tz->ops.change_mode) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = tz->ops.change_mode(tz, mode);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
tz->mode = mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-07-18 19:01:14 +00:00
|
|
|
static void thermal_zone_broken_disable(struct thermal_zone_device *tz)
|
|
|
|
{
|
|
|
|
struct thermal_trip_desc *td;
|
|
|
|
|
|
|
|
dev_err(&tz->device, "Unable to get temperature, disabling!\n");
|
|
|
|
/*
|
|
|
|
* This function only runs for enabled thermal zones, so no need to
|
|
|
|
* check for the current mode.
|
|
|
|
*/
|
|
|
|
__thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
|
|
|
|
thermal_notify_tz_disable(tz);
|
|
|
|
|
|
|
|
for_each_trip_desc(tz, td) {
|
|
|
|
if (td->trip.type == THERMAL_TRIP_CRITICAL &&
|
|
|
|
td->trip.temperature > THERMAL_TEMP_INVALID) {
|
|
|
|
dev_crit(&tz->device,
|
|
|
|
"Disabled thermal zone with critical trip point\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2024-08-22 19:47:36 +00:00
|
|
|
if (delay > HZ)
|
|
|
|
delay = round_jiffies_relative(delay);
|
|
|
|
|
|
|
|
mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, delay);
|
2012-09-18 05:35:04 +00:00
|
|
|
}
|
|
|
|
|
2024-07-18 19:01:14 +00:00
|
|
|
static void thermal_zone_recheck(struct thermal_zone_device *tz, int error)
|
|
|
|
{
|
|
|
|
if (error == -EAGAIN) {
|
|
|
|
thermal_zone_device_set_polling(tz, THERMAL_RECHECK_DELAY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print the message once to reduce log noise. It will be followed by
|
|
|
|
* another one if the temperature cannot be determined after multiple
|
|
|
|
* attempts.
|
|
|
|
*/
|
|
|
|
if (tz->recheck_delay_jiffies == THERMAL_RECHECK_DELAY)
|
|
|
|
dev_info(&tz->device, "Temperature check failed (%d)\n", error);
|
|
|
|
|
|
|
|
thermal_zone_device_set_polling(tz, tz->recheck_delay_jiffies);
|
|
|
|
|
|
|
|
tz->recheck_delay_jiffies += max(tz->recheck_delay_jiffies >> 1, 1ULL);
|
|
|
|
if (tz->recheck_delay_jiffies > THERMAL_MAX_RECHECK_DELAY) {
|
|
|
|
thermal_zone_broken_disable(tz);
|
|
|
|
/*
|
|
|
|
* Restore the original recheck delay value to allow the thermal
|
|
|
|
* zone to try to recover when it is reenabled by user space.
|
|
|
|
*/
|
|
|
|
tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-18 05:35:04 +00:00
|
|
|
static void monitor_thermal_zone(struct thermal_zone_device *tz)
|
|
|
|
{
|
2024-08-26 16:32:36 +00:00
|
|
|
if (tz->passive > 0 && tz->passive_delay_jiffies)
|
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 &&
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
tz->last_temperature != THERMAL_TEMP_INIT) {
|
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
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
tz->temperature = THERMAL_TEMP_INIT;
|
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)
|
|
|
|
{
|
2024-05-28 16:54:11 +00:00
|
|
|
if (trip->type == THERMAL_TRIP_HOT || trip->type == THERMAL_TRIP_CRITICAL)
|
|
|
|
return;
|
|
|
|
|
2024-04-23 19:01:15 +00:00
|
|
|
if (governor->trip_crossed)
|
|
|
|
governor->trip_crossed(tz, trip, crossed_up);
|
|
|
|
}
|
|
|
|
|
2024-05-23 16:00:14 +00:00
|
|
|
static void thermal_trip_crossed(struct thermal_zone_device *tz,
|
|
|
|
const struct thermal_trip *trip,
|
|
|
|
struct thermal_governor *governor,
|
|
|
|
bool crossed_up)
|
|
|
|
{
|
|
|
|
if (crossed_up) {
|
|
|
|
thermal_notify_tz_trip_up(tz, trip);
|
|
|
|
thermal_debug_tz_trip_up(tz, trip);
|
|
|
|
} else {
|
|
|
|
thermal_notify_tz_trip_down(tz, trip);
|
|
|
|
thermal_debug_tz_trip_down(tz, trip);
|
|
|
|
}
|
|
|
|
thermal_governor_trip_crossed(governor, tz, trip, crossed_up);
|
|
|
|
}
|
|
|
|
|
2024-07-08 15:16:00 +00:00
|
|
|
static int thermal_trip_notify_cmp(void *not_used, const struct list_head *a,
|
2024-04-02 19:03:36 +00:00
|
|
|
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);
|
2024-07-08 15:16:00 +00:00
|
|
|
return tda->notify_temp - tdb->notify_temp;
|
2024-04-02 19:03:36 +00:00
|
|
|
}
|
|
|
|
|
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);
|
2024-08-16 08:12:32 +00:00
|
|
|
int low = -INT_MAX, high = INT_MAX;
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
int temp, ret;
|
2022-11-10 15:24:56 +00:00
|
|
|
|
2024-08-26 16:37:16 +00:00
|
|
|
if (tz->suspended || tz->mode != THERMAL_DEVICE_ENABLED)
|
2022-11-10 15:24:56 +00:00
|
|
|
return;
|
|
|
|
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
ret = __thermal_zone_get_temp(tz, &temp);
|
|
|
|
if (ret) {
|
2024-07-18 19:01:14 +00:00
|
|
|
thermal_zone_recheck(tz, ret);
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
return;
|
|
|
|
} else if (temp <= THERMAL_TEMP_INVALID) {
|
|
|
|
/*
|
|
|
|
* Special case: No valid temperature value is available, but
|
|
|
|
* the zone owner does not want the core to do anything about
|
|
|
|
* it. Continue regular zone polling if needed, so that this
|
|
|
|
* function can be called again, but skip everything else.
|
|
|
|
*/
|
2024-06-28 12:10:03 +00:00
|
|
|
goto monitor;
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
}
|
|
|
|
|
2024-07-18 19:01:14 +00:00
|
|
|
tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
|
|
|
|
|
thermal: core: Allow thermal zones to tell the core to ignore them
The iwlwifi wireless driver registers a thermal zone that is only needed
when the network interface handled by it is up and it wants that thermal
zone to be effectively ignored by the core otherwise.
Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone()
if zone temperature is invalid") that could be achieved by returning
an error code from the thermal zone's .get_temp() callback because the
core did not really handle errors returned by it almost at all.
However, commit a8a261774466 made the core attempt to recover from the
situation in which the temperature of a thermal zone cannot be
determined due to errors returned by its .get_temp() and is always
invalid from the core's perspective.
That was done because there are thermal zones in which .get_temp()
returns errors to start with due to some difficulties related to the
initialization ordering, but then it will start to produce valid
temperature values at one point.
Unfortunately, the simple approach taken by commit a8a261774466,
which is to poll the thermal zone periodically until its .get_temp()
callback starts to return valid temperature values, is at odds with
the special thermal zone in iwlwifi in which .get_temp() may always
return an error because its network interface may always be down. If
that happens, every attempt to invoke the thermal zone's .get_temp()
callback resulting in an error causes the thermal core to print a
dev_warn() message to the kernel log which is super-noisy.
To address this problem, make the core handle the case in which
.get_temp() returns 0, but the temperature value returned by it
is not actually valid, in a special way. Namely, make the core
completely ignore the invalid temperature value coming from
.get_temp() in that case, which requires folding in
update_temperature() into its caller and a few related changes.
On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0
and put THERMAL_TEMP_INVALID into the temperature return memory
location instead of returning an error when the firmware is not
running or it is not of the right type.
Also, to clearly separate the handling of invalid temperature
values from the thermal zone initialization, introduce a special
THERMAL_TEMP_INIT value specifically for the latter purpose.
Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid")
Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/
Reported-by: Eric Biggers <ebiggers@kernel.org>
Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
Cc: 6.10+ <stable@vger.kernel.org> # 6.10+
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net
[ rjw: Rebased on top of the current mainline ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 19:45:02 +00:00
|
|
|
tz->last_temperature = tz->temperature;
|
|
|
|
tz->temperature = temp;
|
|
|
|
|
|
|
|
trace_thermal_temperature(tz);
|
|
|
|
|
|
|
|
thermal_genl_sampling_temp(tz->id, temp);
|
2024-04-30 15:45:55 +00:00
|
|
|
|
2022-11-10 15:24:56 +00:00
|
|
|
tz->notify_event = event;
|
|
|
|
|
2024-08-16 08:12:32 +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);
|
|
|
|
|
2024-08-16 08:12:32 +00:00
|
|
|
if (td->threshold <= tz->temperature && td->threshold > low)
|
|
|
|
low = td->threshold;
|
|
|
|
|
|
|
|
if (td->threshold >= tz->temperature && td->threshold < high)
|
|
|
|
high = td->threshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
thermal_zone_set_trips(tz, low, high);
|
2024-05-28 16:51:11 +00:00
|
|
|
|
2024-07-08 15:16:00 +00:00
|
|
|
list_sort(NULL, &way_up_list, thermal_trip_notify_cmp);
|
2024-05-23 16:00:14 +00:00
|
|
|
list_for_each_entry(td, &way_up_list, notify_list_node)
|
|
|
|
thermal_trip_crossed(tz, &td->trip, governor, true);
|
2024-04-02 19:03:36 +00:00
|
|
|
|
|
|
|
list_sort(NULL, &way_down_list, thermal_trip_notify_cmp);
|
2024-07-08 15:16:00 +00:00
|
|
|
list_for_each_entry_reverse(td, &way_down_list, notify_list_node)
|
2024-05-23 16:00:14 +00:00
|
|
|
thermal_trip_crossed(tz, &td->trip, governor, false);
|
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
|
|
|
|
2024-06-28 12:10:03 +00:00
|
|
|
monitor:
|
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)
|
|
|
|
{
|
2024-07-18 19:00:35 +00:00
|
|
|
int ret;
|
2020-06-29 12:29:20 +00:00
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
/* do nothing if mode isn't changing */
|
|
|
|
if (mode == tz->mode) {
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
2024-07-18 19:00:35 +00:00
|
|
|
return 0;
|
2020-06-29 12:29:20 +00:00
|
|
|
}
|
|
|
|
|
2024-07-18 19:00:35 +00:00
|
|
|
ret = __thermal_zone_device_set_mode(tz, mode);
|
|
|
|
if (ret) {
|
|
|
|
mutex_unlock(&tz->lock);
|
2020-06-29 12:29:20 +00:00
|
|
|
|
2024-07-18 19:00:35 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2020-06-29 12:29:20 +00:00
|
|
|
|
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
|
|
|
|
2024-07-18 19:00:35 +00:00
|
|
|
return 0;
|
2020-06-29 12:29:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
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
|
|
|
|
2024-05-23 16:05:03 +00:00
|
|
|
void thermal_zone_trip_down(struct thermal_zone_device *tz,
|
|
|
|
const struct thermal_trip *trip)
|
|
|
|
{
|
|
|
|
thermal_trip_crossed(tz, trip, thermal_get_tz_governor(tz), false);
|
|
|
|
}
|
|
|
|
|
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) {
|
2024-10-03 12:25:58 +00:00
|
|
|
get_device(&tz->device);
|
2020-07-24 17:01:05 +00:00
|
|
|
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
|
2024-08-21 15:26:28 +00:00
|
|
|
* @cool_spec: cooling specification for @trip and @cdev
|
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
|
|
|
*/
|
2024-08-19 16:05:00 +00:00
|
|
|
static int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz,
|
2023-09-21 18:01:43 +00:00
|
|
|
const struct thermal_trip *trip,
|
2012-06-26 08:35:57 +00:00
|
|
|
struct thermal_cooling_device *cdev,
|
2024-08-21 15:26:28 +00:00
|
|
|
struct cooling_spec *cool_spec)
|
2008-01-17 07:51:08 +00:00
|
|
|
{
|
2012-06-27 02:08:19 +00:00
|
|
|
struct thermal_instance *dev;
|
|
|
|
struct thermal_instance *pos;
|
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
|
|
|
|
2012-06-26 08:35:57 +00:00
|
|
|
/* lower default 0, upper default max_state */
|
2024-08-21 15:26:28 +00:00
|
|
|
if (cool_spec->lower == THERMAL_NO_LIMIT)
|
|
|
|
cool_spec->lower = 0;
|
2023-03-17 17:01:26 +00:00
|
|
|
|
2024-08-21 15:26:28 +00:00
|
|
|
if (cool_spec->upper == THERMAL_NO_LIMIT) {
|
|
|
|
cool_spec->upper = cdev->max_state;
|
2023-03-17 17:01:26 +00:00
|
|
|
upper_no_limit = true;
|
|
|
|
} else {
|
|
|
|
upper_no_limit = false;
|
|
|
|
}
|
2012-06-26 08:35:57 +00:00
|
|
|
|
2024-08-21 15:26:28 +00:00
|
|
|
if (cool_spec->lower > cool_spec->upper || cool_spec->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;
|
2024-08-22 19:27:06 +00:00
|
|
|
|
2008-01-17 07:51:08 +00:00
|
|
|
dev->cdev = cdev;
|
|
|
|
dev->trip = trip;
|
2024-08-21 15:26:28 +00:00
|
|
|
dev->upper = cool_spec->upper;
|
2023-03-17 17:01:26 +00:00
|
|
|
dev->upper_no_limit = upper_no_limit;
|
2024-08-21 15:26:28 +00:00
|
|
|
dev->lower = cool_spec->lower;
|
2012-06-27 06:13:04 +00:00
|
|
|
dev->target = THERMAL_NO_TARGET;
|
2024-08-21 15:26:28 +00:00
|
|
|
dev->weight = cool_spec->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;
|
|
|
|
|
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)
|
2024-08-19 15:52:54 +00:00
|
|
|
if (pos->trip == trip && pos->cdev == cdev) {
|
2016-11-08 05:09:23 +00:00
|
|
|
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
|
|
|
|
|
|
|
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
|
|
|
|
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.
|
2008-01-17 07:51:08 +00:00
|
|
|
*/
|
2024-08-21 15:26:28 +00:00
|
|
|
static void 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
|
|
|
|
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) {
|
2024-08-19 15:52:54 +00:00
|
|
|
if (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
|
|
|
goto unbind;
|
|
|
|
}
|
|
|
|
}
|
2012-07-24 08:56:21 +00:00
|
|
|
mutex_unlock(&cdev->lock);
|
2008-01-17 07:51:08 +00:00
|
|
|
|
2024-08-21 15:26:28 +00:00
|
|
|
return;
|
2008-01-17 07:51:08 +00:00
|
|
|
|
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);
|
|
|
|
}
|
2023-09-21 18:01:43 +00:00
|
|
|
|
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,
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
const struct thermal_trip *trip,
|
2016-11-08 05:09:13 +00:00
|
|
|
struct thermal_cooling_device *cdev, int ret)
|
2016-11-08 05:09:12 +00:00
|
|
|
{
|
2024-08-19 16:31:33 +00:00
|
|
|
dev_err(&tz->device, "binding cdev %s to trip %d failed: %d\n",
|
|
|
|
cdev->type, thermal_zone_trip_id(tz, trip), ret);
|
2016-11-08 05:09:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-22 19:24:35 +00:00
|
|
|
static void thermal_zone_cdev_bind(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_cooling_device *cdev)
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
{
|
|
|
|
struct thermal_trip_desc *td;
|
|
|
|
|
|
|
|
if (!tz->ops.should_bind)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
for_each_trip_desc(tz, td) {
|
|
|
|
struct thermal_trip *trip = &td->trip;
|
|
|
|
struct cooling_spec c = {
|
|
|
|
.upper = THERMAL_NO_LIMIT,
|
|
|
|
.lower = THERMAL_NO_LIMIT,
|
|
|
|
.weight = THERMAL_WEIGHT_DEFAULT
|
|
|
|
};
|
2024-08-21 15:26:28 +00:00
|
|
|
int ret;
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
|
2024-08-21 15:26:28 +00:00
|
|
|
if (!tz->ops.should_bind(tz, trip, cdev, &c))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = thermal_bind_cdev_to_trip(tz, trip, cdev, &c);
|
|
|
|
if (ret)
|
|
|
|
print_bind_err_msg(tz, trip, cdev, ret);
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
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-06-06 18:27:19 +00:00
|
|
|
/*
|
|
|
|
* The cooling device's current state is only needed for debug
|
|
|
|
* initialization below, so a failure to get it does not cause
|
|
|
|
* the entire cooling device initialization to fail. However,
|
|
|
|
* the debug will not work for the device if its initial state
|
|
|
|
* cannot be determined and drivers are responsible for ensuring
|
|
|
|
* that this will not happen.
|
|
|
|
*/
|
2024-04-25 12:24:20 +00:00
|
|
|
ret = cdev->ops->get_cur_state(cdev, ¤t_state);
|
|
|
|
if (ret)
|
2024-06-06 18:27:19 +00:00
|
|
|
current_state = ULONG_MAX;
|
2024-04-25 12:24:20 +00:00
|
|
|
|
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-06-06 18:27:19 +00:00
|
|
|
if (current_state <= cdev->max_state)
|
|
|
|
thermal_debug_cdev_add(cdev, current_state);
|
2024-04-25 12:24:20 +00:00
|
|
|
|
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 */
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
list_for_each_entry(pos, &thermal_tz_list, node)
|
2024-08-22 19:24:35 +00:00
|
|
|
thermal_zone_cdev_bind(pos, cdev);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
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,
|
2024-07-03 08:31:41 +00:00
|
|
|
const char *type, void *devdata,
|
2019-04-18 19:58:15 +00:00
|
|
|
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);
|
|
|
|
|
2024-08-22 19:24:35 +00:00
|
|
|
static void thermal_zone_cdev_unbind(struct thermal_zone_device *tz,
|
|
|
|
struct thermal_cooling_device *cdev)
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
{
|
|
|
|
struct thermal_trip_desc *td;
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
|
|
|
|
for_each_trip_desc(tz, td)
|
|
|
|
thermal_unbind_cdev_from_trip(tz, &td->trip, cdev);
|
|
|
|
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
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 */
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
list_for_each_entry(tz, &thermal_tz_list, node)
|
2024-08-22 19:24:35 +00:00
|
|
|
thermal_zone_cdev_unbind(tz, cdev);
|
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
|
|
|
|
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,
|
2024-07-05 19:44:50 +00:00
|
|
|
unsigned int passive_delay,
|
|
|
|
unsigned 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;
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
struct thermal_cooling_device *cdev;
|
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
|
|
|
|
2024-08-19 16:31:33 +00:00
|
|
|
if (!ops || !ops->get_temp) {
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
pr_err("Thermal zone device ops not defined or invalid\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);
|
|
|
|
|
2024-08-26 16:32:36 +00:00
|
|
|
if (polling_delay && passive_delay > polling_delay)
|
|
|
|
return ERR_PTR(-EINVAL);
|
2024-07-11 12:39:02 +00:00
|
|
|
|
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);
|
2024-06-14 15:22:25 +00:00
|
|
|
init_completion(&tz->resume);
|
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
|
|
|
|
2024-08-22 19:47:36 +00:00
|
|
|
tz->polling_delay_jiffies = msecs_to_jiffies(polling_delay);
|
|
|
|
tz->passive_delay_jiffies = msecs_to_jiffies(passive_delay);
|
2024-07-18 19:01:14 +00:00
|
|
|
tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
|
2020-12-16 22:03:35 +00:00
|
|
|
|
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);
|
2024-08-19 15:50:30 +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
|
|
|
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
|
|
|
|
2012-09-18 05:34:59 +00:00
|
|
|
/* Bind cooling devices for this zone */
|
thermal: core: Introduce .should_bind() thermal zone callback
The current design of the code binding cooling devices to trip points in
thermal zones is convoluted and hard to follow.
Namely, a driver that registers a thermal zone can provide .bind()
and .unbind() operations for it, which are required to call either
thermal_bind_cdev_to_trip() and thermal_unbind_cdev_from_trip(),
respectively, or thermal_zone_bind_cooling_device() and
thermal_zone_unbind_cooling_device(), respectively, for every relevant
trip point and the given cooling device. Moreover, if .bind() is
provided and .unbind() is not, the cleanup necessary during the removal
of a thermal zone or a cooling device may not be carried out.
In other words, the core relies on the thermal zone owners to do the
right thing, which is error prone and far from obvious, even though all
of that is not really necessary. Specifically, if the core could ask
the thermal zone owner, through a special thermal zone callback, whether
or not a given cooling device should be bound to a given trip point in
the given thermal zone, it might as well carry out all of the binding
and unbinding by itself. In particular, the unbinding can be done
automatically without involving the thermal zone owner at all because
all of the thermal instances associated with a thermal zone or cooling
device going away must be deleted regardless.
Accordingly, introduce a new thermal zone operation, .should_bind(),
that can be invoked by the thermal core for a given thermal zone,
trip point and cooling device combination in order to check whether
or not the cooling device should be bound to the trip point at hand.
It takes an additional cooling_spec argument allowing the thermal
zone owner to specify the highest and lowest cooling states of the
cooling device and its weight for the given trip point binding.
Make the thermal core use this operation, if present, in the absence of
.bind() and .unbind(). Note that .should_bind() will be called under
the thermal zone lock.
No intentional functional impact.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Zhang Rui <rui.zhang@intel.com>
Acked-by: Huisong Li <lihuisong@huawei.com>
Reviewed-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://patch.msgid.link/9334403.CDJkKcVGEf@rjwysocki.net
2024-08-19 16:00:19 +00:00
|
|
|
list_for_each_entry(cdev, &thermal_cdev_list, node)
|
2024-08-22 19:24:35 +00:00
|
|
|
thermal_zone_cdev_bind(tz, cdev);
|
2024-08-19 15:50:30 +00:00
|
|
|
|
|
|
|
mutex_unlock(&thermal_list_lock);
|
2012-09-18 05:34:59 +00:00
|
|
|
|
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-08-22 19:24:35 +00:00
|
|
|
thermal_zone_cdev_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);
|
|
|
|
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);
|
2024-10-03 12:27:28 +00:00
|
|
|
kfree(tz->tzp);
|
2023-12-08 19:13:44 +00:00
|
|
|
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;
|
|
|
|
|
2024-05-28 14:53:47 +00:00
|
|
|
thermal_debug_tz_resume(tz);
|
2023-12-18 19:28:31 +00:00
|
|
|
thermal_zone_device_init(tz);
|
2024-08-13 14:29:11 +00:00
|
|
|
thermal_governor_update_tz(tz, THERMAL_TZ_RESUME);
|
|
|
|
__thermal_zone_device_update(tz, THERMAL_TZ_RESUME);
|
2023-12-18 19:28:31 +00:00
|
|
|
|
2024-06-14 15:22:25 +00:00
|
|
|
complete(&tz->resume);
|
|
|
|
tz->resuming = false;
|
|
|
|
|
2023-12-18 19:28:31 +00:00
|
|
|
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);
|
|
|
|
|
2024-06-14 15:22:25 +00:00
|
|
|
if (tz->resuming) {
|
|
|
|
/*
|
|
|
|
* thermal_zone_device_resume() queued up for
|
|
|
|
* this zone has not acquired the lock yet, so
|
|
|
|
* release it to let the function run and wait
|
|
|
|
* util it has done the work.
|
|
|
|
*/
|
|
|
|
mutex_unlock(&tz->lock);
|
|
|
|
|
|
|
|
wait_for_completion(&tz->resume);
|
|
|
|
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
}
|
|
|
|
|
2023-12-18 19:25:02 +00:00
|
|
|
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);
|
|
|
|
|
2024-06-14 15:22:25 +00:00
|
|
|
reinit_completion(&tz->resume);
|
|
|
|
tz->resuming = true;
|
|
|
|
|
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,
|
2024-06-14 15:26:00 +00:00
|
|
|
/*
|
|
|
|
* Run at the lowest priority to avoid interference between the thermal
|
|
|
|
* zone resume work items spawned by thermal_pm_notify() and the other
|
|
|
|
* PM notifiers.
|
|
|
|
*/
|
|
|
|
.priority = INT_MIN,
|
2015-10-30 08:31:58 +00:00
|
|
|
};
|
|
|
|
|
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);
|