mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
e528be3c87
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 commita8a2617744
("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, commita8a2617744
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 commita8a2617744
, 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:a8a2617744
("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>
261 lines
6.4 KiB
C
261 lines
6.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* thermal_helpers.c - helper functions to handle thermal devices
|
|
*
|
|
* Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com>
|
|
*
|
|
* Highly based on original thermal_core.c
|
|
* Copyright (C) 2008 Intel Corp
|
|
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
|
|
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
#include "thermal_core.h"
|
|
#include "thermal_trace.h"
|
|
|
|
int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip)
|
|
{
|
|
enum thermal_trend trend;
|
|
|
|
if (tz->emul_temperature || !tz->ops.get_trend ||
|
|
tz->ops.get_trend(tz, trip, &trend)) {
|
|
if (tz->temperature > tz->last_temperature)
|
|
trend = THERMAL_TREND_RAISING;
|
|
else if (tz->temperature < tz->last_temperature)
|
|
trend = THERMAL_TREND_DROPPING;
|
|
else
|
|
trend = THERMAL_TREND_STABLE;
|
|
}
|
|
|
|
return trend;
|
|
}
|
|
|
|
static struct thermal_instance *get_instance(struct thermal_zone_device *tz,
|
|
struct thermal_cooling_device *cdev,
|
|
const struct thermal_trip *trip)
|
|
{
|
|
struct thermal_instance *ti;
|
|
|
|
list_for_each_entry(ti, &tz->thermal_instances, tz_node) {
|
|
if (ti->trip == trip && ti->cdev == cdev)
|
|
return ti;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool thermal_trip_is_bound_to_cdev(struct thermal_zone_device *tz,
|
|
const struct thermal_trip *trip,
|
|
struct thermal_cooling_device *cdev)
|
|
{
|
|
bool ret;
|
|
|
|
mutex_lock(&tz->lock);
|
|
mutex_lock(&cdev->lock);
|
|
|
|
ret = !!get_instance(tz, cdev, trip);
|
|
|
|
mutex_unlock(&cdev->lock);
|
|
mutex_unlock(&tz->lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(thermal_trip_is_bound_to_cdev);
|
|
|
|
struct thermal_instance *
|
|
get_thermal_instance(struct thermal_zone_device *tz,
|
|
struct thermal_cooling_device *cdev, int trip_index)
|
|
{
|
|
struct thermal_instance *ti;
|
|
|
|
mutex_lock(&tz->lock);
|
|
mutex_lock(&cdev->lock);
|
|
|
|
ti = get_instance(tz, cdev, &tz->trips[trip_index].trip);
|
|
|
|
mutex_unlock(&cdev->lock);
|
|
mutex_unlock(&tz->lock);
|
|
|
|
return ti;
|
|
}
|
|
EXPORT_SYMBOL(get_thermal_instance);
|
|
|
|
/**
|
|
* __thermal_zone_get_temp() - returns the temperature of a thermal zone
|
|
* @tz: a valid pointer to a struct thermal_zone_device
|
|
* @temp: a valid pointer to where to store the resulting temperature.
|
|
*
|
|
* When a valid thermal zone reference is passed, it will fetch its
|
|
* temperature and fill @temp.
|
|
*
|
|
* Both tz and tz->ops must be valid pointers when calling this function,
|
|
* and the tz->ops.get_temp callback must be provided.
|
|
* The function must be called under tz->lock.
|
|
*
|
|
* Return: On success returns 0, an error code otherwise
|
|
*/
|
|
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
|
|
{
|
|
const struct thermal_trip_desc *td;
|
|
int crit_temp = INT_MAX;
|
|
int ret = -EINVAL;
|
|
|
|
lockdep_assert_held(&tz->lock);
|
|
|
|
ret = tz->ops.get_temp(tz, temp);
|
|
|
|
if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
|
|
for_each_trip_desc(tz, td) {
|
|
const struct thermal_trip *trip = &td->trip;
|
|
|
|
if (trip->type == THERMAL_TRIP_CRITICAL) {
|
|
crit_temp = trip->temperature;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Only allow emulating a temperature when the real temperature
|
|
* is below the critical temperature so that the emulation code
|
|
* cannot hide critical conditions.
|
|
*/
|
|
if (!ret && *temp < crit_temp)
|
|
*temp = tz->emul_temperature;
|
|
}
|
|
|
|
if (ret)
|
|
dev_dbg(&tz->device, "Failed to get temperature: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* thermal_zone_get_temp() - returns the temperature of a thermal zone
|
|
* @tz: a valid pointer to a struct thermal_zone_device
|
|
* @temp: a valid pointer to where to store the resulting temperature.
|
|
*
|
|
* When a valid thermal zone reference is passed, it will fetch its
|
|
* temperature and fill @temp.
|
|
*
|
|
* Return: On success returns 0, an error code otherwise
|
|
*/
|
|
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
|
|
{
|
|
int ret;
|
|
|
|
if (IS_ERR_OR_NULL(tz))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&tz->lock);
|
|
|
|
if (!tz->ops.get_temp) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
ret = __thermal_zone_get_temp(tz, temp);
|
|
if (!ret && *temp <= THERMAL_TEMP_INVALID)
|
|
ret = -ENODATA;
|
|
|
|
unlock:
|
|
mutex_unlock(&tz->lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
|
|
|
|
static int thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int state)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* No check is needed for the ops->set_cur_state as the
|
|
* registering function checked the ops are correctly set
|
|
*/
|
|
ret = cdev->ops->set_cur_state(cdev, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
thermal_notify_cdev_state_update(cdev, state);
|
|
thermal_cooling_device_stats_update(cdev, state);
|
|
thermal_debug_cdev_state_update(cdev, state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __thermal_cdev_update(struct thermal_cooling_device *cdev)
|
|
{
|
|
struct thermal_instance *instance;
|
|
unsigned long target = 0;
|
|
|
|
/* Make sure cdev enters the deepest cooling state */
|
|
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
|
|
dev_dbg(&cdev->device, "zone%d->target=%lu\n",
|
|
instance->tz->id, instance->target);
|
|
if (instance->target == THERMAL_NO_TARGET)
|
|
continue;
|
|
if (instance->target > target)
|
|
target = instance->target;
|
|
}
|
|
|
|
thermal_cdev_set_cur_state(cdev, target);
|
|
|
|
trace_cdev_update(cdev, target);
|
|
dev_dbg(&cdev->device, "set to state %lu\n", target);
|
|
}
|
|
|
|
/**
|
|
* thermal_cdev_update - update cooling device state if needed
|
|
* @cdev: pointer to struct thermal_cooling_device
|
|
*
|
|
* Update the cooling device state if there is a need.
|
|
*/
|
|
void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
|
{
|
|
mutex_lock(&cdev->lock);
|
|
if (!cdev->updated) {
|
|
__thermal_cdev_update(cdev);
|
|
cdev->updated = true;
|
|
}
|
|
mutex_unlock(&cdev->lock);
|
|
}
|
|
|
|
/**
|
|
* thermal_zone_get_slope - return the slope attribute of the thermal zone
|
|
* @tz: thermal zone device with the slope attribute
|
|
*
|
|
* Return: If the thermal zone device has a slope attribute, return it, else
|
|
* return 1.
|
|
*/
|
|
int thermal_zone_get_slope(struct thermal_zone_device *tz)
|
|
{
|
|
if (tz && tz->tzp)
|
|
return tz->tzp->slope;
|
|
return 1;
|
|
}
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
|
|
|
|
/**
|
|
* thermal_zone_get_offset - return the offset attribute of the thermal zone
|
|
* @tz: thermal zone device with the offset attribute
|
|
*
|
|
* Return: If the thermal zone device has a offset attribute, return it, else
|
|
* return 0.
|
|
*/
|
|
int thermal_zone_get_offset(struct thermal_zone_device *tz)
|
|
{
|
|
if (tz && tz->tzp)
|
|
return tz->tzp->offset;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(thermal_zone_get_offset);
|