admin管理员组文章数量:1536371
目录
一、概述
二、源码分析(基于kernel-4.19)
2.1 thermal_init(drivers\thermal\thermal_core.c)
2.2 thermal_register_governors
2.2.1 thermal_register_governor
2.2.2 step_wise(drivers\thermal\step_wise.c)
2.2.3 fair_share(drivers\thermal\fair_share.c)
2.2.4 bang_bang(drivers\thermal\gov_bang_bang.c)
2.2.5 power_allocator(drivers\thermal\power_allocator.c)
2.2.6 user_space
2.3 class_register
2.4 of_parse_thermal_zones
一、概述
Linux Thermal是linux系统下温度控制相关模块,主要作用是让系统温度保持在安全的范围,为了实现这个目的需要获取温度的设备和控制温度的设备,以及一些温度控制设备的策略。
获取温度的设备在Thermal框架中被抽象为Thermal Zone Device;
控制温度的设备在Thermal框架中被抽象为Thermal Cooling Device;
温控策略在Thermal框架中被抽象为Thermal governors;
Thermal core,thermal主要的程序,驱动初始化程序,组织并管理上面三个组件,通过sysfs和用户空间交互
Cooling Device维护一个Cooling等级(state),Cooling Device只根据state进行冷却操作,state的计算由governor完成;thermal_instance结构体描述trip point必须与一个cooling device绑定,一个thermal zone可以有多个cooling设备,同时还提供一个核心函数thermal_zone_device_update作为thermal中断处理和轮询函数,轮询时间会根据不同trip delay调节,发热设备也可以是降温设备,比如CPU、GPU,频率升高时是升温设备,频率降低时是降温设备
thermal governor,解决温控发生时(throttle),cooling device如何选择cooling state的问题,linux内核提供了多种温控策略:
1.step_wise:根据当前温度,cooling device逐级降频
2.power_allocator:引入PID(比例-积分-微分)控制,根据当前温度,动态给各cooling device分配power,并将power转换为频率,从而达到根据温度限制频率的效果
3.user_space:用户空间控制
4.fair_share:频率挡位比较多的cooling device优先降频
5.bang_bang:两点温度调节,可用于cooling device由风扇的场景
如果dts中没有指定使用的governor就使用默认策略,默认governor位置(drivers/thermal/Kconfig)
thermal zone device,创建thermal zone节点和连接thermal sensor,在sys/class/thermal目录下的thermal_zone*,通过dts文件进行配置生成,thermal sensor是温度传感器
二、源码分析(基于kernel-4.19)
2.1 thermal_init(drivers\thermal\thermal_core.c)
static int __init thermal_init(void)
{
int result;
mutex_init(&poweroff_lock);//注册互斥锁
result = thermal_register_governors();//向thermal zone注册thermal_governor
if (result)
goto error;
result = class_register(&thermal_class);//注册class,sys/class/thermal
if (result)
goto unregister_governors;
result = genetlink_init();//注册netlink:用于内核与用户空间进程之间以及不同进程间通信
if (result)
goto unregister_class;
result = of_parse_thermal_zones();//解析dts中的thermal_zone,并注册thermal_zone_device
if (result)
goto exit_netlink;
result = register_pm_notifier(&thermal_pm_nb);//注册notifier机制
if (result)
pr_warn("Thermal: Can not register suspend notifier, return %d\n",
result);
return 0;
exit_netlink:
genetlink_exit();
unregister_class:
class_unregister(&thermal_class);
unregister_governors:
thermal_unregister_governors();
error:
ida_destroy(&thermal_tz_ida);
ida_destroy(&thermal_cdev_ida);
mutex_destroy(&thermal_list_lock);
mutex_destroy(&thermal_governor_lock);
mutex_destroy(&poweroff_lock);
return result;
}
可以看到代码流程就thermal_register_governors-->class_register-->genetlink_init-->of_parse_thermal_zones-->register_pm_notifier
其中genetlink、notifier大致知道是干啥的就行,不做研究
2.2 thermal_register_governors
static int __init thermal_register_governors(void)//依次注册各governor
{
int result;
result = thermal_gov_step_wise_register();
if (result)
return result;
result = thermal_gov_fair_share_register();
if (result)
return result;
result = thermal_gov_bang_bang_register();
if (result)
return result;
result = thermal_gov_user_space_register();
if (result)
return result;
return thermal_gov_power_allocator_register();
}
- 走的都是thermal_register_governor,只是传入的参数不同
2.2.1 thermal_register_governor
int thermal_register_governor(struct thermal_governor *governor)
{
int err;
const char *name;
struct thermal_zone_device *pos;
if (!governor)//指针判空
return -EINVAL;
mutex_lock(&thermal_governor_lock);
err = -EBUSY;
if (!__find_governor(governor->name)) {//遍历链表查找该governor是否已注册
bool match_default;
err = 0;
list_add(&governor->governor_list, &thermal_governor_list);//没有注册则添加进链表中
match_default = !strncmp(governor->name,
DEFAULT_THERMAL_GOVERNOR,
THERMAL_NAME_LENGTH);
if (!def_governor && match_default)//判断是否与默认策略相同,相同则赋值给def_governor
def_governor = governor;
}
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {//遍历链表
/*
* only thermal zones with specified tz->tzp->governor_name
* may run with tz->govenor unset
*/
if (pos->governor)//找到注册策略的thermal zone device
continue;
name = pos->tzp->governor_name;
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);
}
}
mutex_unlock(&thermal_list_lock);
mutex_unlock(&thermal_governor_lock);
return err;
}
下面看具体的各个策略
2.2.2 step_wise(drivers\thermal\step_wise.c)
static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
thermal_zone_trip_update(tz, trip);//更新trip、trend和计算cooling_device的等级
if (tz->forced_passive)//默认触发温度
thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);//重新设置trip,这里传入为-1表示啥也不做
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node)//遍历各instances
thermal_cdev_update(instance->cdev);//更新cooling_device
mutex_unlock(&tz->lock);
return 0;
}
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
int trip_temp;
enum thermal_trip_type trip_type;
enum thermal_trend trend;
struct thermal_instance *instance;
bool throttle = false;
int old_target;
if (trip == THERMAL_TRIPS_NONE) {//没有trip
trip_temp = tz->forced_passive;//则触发温度为默认值
trip_type = THERMAL_TRIPS_NONE;//无触发类型
} else {
tz->ops->get_trip_temp(tz, trip, &trip_temp);//获取trip温度
tz->ops->get_trip_type(tz, trip, &trip_type);//获取trip类型
}
trend = get_tz_trend(tz, trip);//获取温度的趋势,是用当前温度比较上一次获取的温度
//这里的趋势共三种上升、下降、稳定
if (tz->temperature >= trip_temp) {//达到触发温度时
throttle = true;
trace_thermal_zone_trip(tz, trip, trip_type);
}
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
trip, trip_type, trip_temp, trend, throttle);
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历instance链表
if (instance->trip != trip)//找到与trip对应的instance
continue;
old_target = instance->target;
instance->target = get_target_state(instance, trend, throttle);//获取cooling device的等级
dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
old_target, (int)instance->target);
if (instance->initialized && old_target == instance->target)//instance以及初始化且等级不变则不走下面流程
continue;//只有等级发生变化才会走下面
/* Activate a passive thermal instance */
if (old_target == THERMAL_NO_TARGET &&//上一等级啥也不做则当前等级不为-1
instance->target != THERMAL_NO_TARGET)
update_passive_instance(tz, trip_type, 1);//启动温控
/* Deactivate a passive thermal instance */
else if (old_target != THERMAL_NO_TARGET &&//上一等级不为空且当前等级为空
instance->target == THERMAL_NO_TARGET)
update_passive_instance(tz, trip_type, -1);//不启动温控
instance->initialized = true;//初始化标志位赋值true,表明已初始化
mutex_lock(&instance->cdev->lock);
instance->cdev->updated = false; /* cdev needs update *///更新标志位写为false表示需要更新
mutex_unlock(&instance->cdev->lock);
}
mutex_unlock(&tz->lock);
}
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
struct thermal_instance *instance;
unsigned long target = 0;
mutex_lock(&cdev->lock);
/* cooling device is updated*/
if (cdev->updated) {//判断更新标志位,检查是否需要更新
mutex_unlock(&cdev->lock);
return;
}
/* Make sure cdev enters the deepest cooling state */
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {//遍历instances链表
dev_dbg(&cdev->device, "zone%d->target=%lu\n",
instance->tz->id, instance->target);
if (instance->target == THERMAL_NO_TARGET)//非-1才走下面
continue;
if (instance->target > target)
target = instance->target;//更新target
}
if (!cdev->ops->set_cur_state(cdev, target))//设置当前等级
thermal_cooling_device_stats_update(cdev, target);//设置失败则走更新状态
cdev->updated = true;
mutex_unlock(&cdev->lock);
trace_cdev_update(cdev, target);
dev_dbg(&cdev->device, "set to state %lu\n", target);
}
EXPORT_SYMBOL(thermal_cdev_update);
该策略主要逻辑get_target_state
static unsigned long get_target_state(struct thermal_instance *instance,
enum thermal_trend trend, bool throttle)
{
struct thermal_cooling_device *cdev = instance->cdev;
unsigned long cur_state;
unsigned long next_target;
/*
* We keep this instance the way it is by default.
* Otherwise, we use the current state of the
* cdev in use to determine the next_target.
*/
cdev->ops->get_cur_state(cdev, &cur_state);//获取当前等级
next_target = instance->target;
dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
if (!instance->initialized) {//判断是否初始化
if (throttle) {//触发温控
next_target = (cur_state + 1) >= instance->upper ?//当前等级+1是否等级最高等级,是则下一等级为最高等级
//不是则判断是否低于最低等级,低于则下一等级为最低,否则为当前等级+1,都不满足则啥也不做
instance->upper :
((cur_state + 1) < instance->lower ?
instance->lower : (cur_state + 1));
} else {
next_target = THERMAL_NO_TARGET;
}
return next_target;
}
switch (trend) {//根据温度趋势来判断
case THERMAL_TREND_RAISING://温度上升时
if (throttle) {//触发温控
next_target = cur_state < instance->upper ?//当前等级是否低于最高等级,是则下一等级为当前等级+1,否则为最高等级
(cur_state + 1) : instance->upper;
if (next_target < instance->lower)//上面判断后当前等级还是低于最低等级的话则下一等级为最低等级
next_target = instance->lower;//这里的等级指的是温控的等级,等级越高说明温控越严重,需要更高等级的限制
}
break;
case THERMAL_TREND_RAISE_FULL://已经是最高温度了
if (throttle)//触发温控
next_target = instance->upper;//最高等级
break;
case THERMAL_TREND_DROPPING://温度下降时
if (cur_state <= instance->lower) {//当前等级是否小于最低等级
if (!throttle)//没触发温控
next_target = THERMAL_NO_TARGET;//啥也不做
} else {//不小于最低等级
if (!throttle) {//触发温控
next_target = cur_state - 1;//下一等级为当前等级-1
if (next_target > instance->upper)//上面判断后当前等级大于最高等级则为最高等级
next_target = instance->upper;
}
}
break;
case THERMAL_TREND_DROP_FULL://最低温度了
if (cur_state == instance->lower) {//当前为最低等级
if (!throttle)//没触发温控
next_target = THERMAL_NO_TARGET;//啥也不做
} else//不是最低等级
next_target = instance->lower;//下一等级为最低等级
break;
default:
break;
}
return next_target;
}
从上面源码可以看出step_wise是根据当前的温度来逐步提高或下降等级
2.2.3 fair_share(drivers\thermal\fair_share.c)
static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
int total_weight = 0;
int total_instance = 0;
int cur_trip_level = get_trip_level(tz);//获取当前的trip等级
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)//遍历链表找到不同trip的instance
continue;
total_weight += instance->weight;//统计instance的weight即权重
total_instance++;//统计总的instance
}
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
int percentage;
struct thermal_cooling_device *cdev = instance->cdev;
if (instance->trip != trip)
continue;
if (!total_weight)//总权重无效
percentage = 100 / total_instance;//得到百分比
else//总权重有效
percentage = (instance->weight * 100) / total_weight;//则百分比由权重计算出,与权重相关
instance->target = get_target_state(tz, cdev, percentage,
cur_trip_level);//计算目标等级,与百分比相关,即正常情况下与权重相关
mutex_lock(&instance->cdev->lock);
instance->cdev->updated = false;
mutex_unlock(&instance->cdev->lock);
thermal_cdev_update(cdev);
}
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor thermal_gov_fair_share = {
.name = "fair_share",
.throttle = fair_share_throttle,
};
int thermal_gov_fair_share_register(void)
{
return thermal_register_governor(&thermal_gov_fair_share);
}
static int get_trip_level(struct thermal_zone_device *tz)
{
int count = 0;
int trip_temp;
enum thermal_trip_type trip_type;
if (tz->trips == 0 || !tz->ops->get_trip_temp)//未实现get_trip_temp获取trips为0
return 0;
for (count = 0; count < tz->trips; count++) {//遍历trips
tz->ops->get_trip_temp(tz, count, &trip_temp);//获取触发温度
if (tz->temperature < trip_temp)
break;
}
/*
* count > 0 only if temperature is greater than first trip
* point, in which case, trip_point = count - 1
*/
if (count > 0) {
tz->ops->get_trip_type(tz, count - 1, &trip_type);//获取触发类型
trace_thermal_zone_trip(tz, count - 1, trip_type);
}
return count;
}
static long get_target_state(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int percentage, int level)
{
unsigned long max_state;
cdev->ops->get_max_state(cdev, &max_state);//得到最大等级
return (long)(percentage * level * max_state) / (100 * tz->trips);//
}
fair_share为频率挡位较多的cooling device优先降频,频率挡位较多即trip多则权重就高,计算出的等级就高
2.2.4 bang_bang(drivers\thermal\gov_bang_bang.c)
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
int trip_temp, trip_hyst;
struct thermal_instance *instance;
tz->ops->get_trip_temp(tz, trip, &trip_temp);//获取目标温度
if (!tz->ops->get_trip_hyst) {//如果没有实现get_trip_hyst
pr_warn_once("Undefined get_trip_hyst for thermal zone %s - "
"running with default hysteresis zero\n", tz->type);
trip_hyst = 0;
} else
tz->ops->get_trip_hyst(tz, trip, &trip_hyst);//获取目标滞后
dev_dbg(&tz->device, "Trip%d[temp=%d]:temp=%d:hyst=%d\n",
trip, trip_temp, tz->temperature,
trip_hyst);
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)//遍历instance找到对应trip
continue;
/* in case fan is in initial state, switch the fan off */
if (instance->target == THERMAL_NO_TARGET)//啥也不做
instance->target = 0;//则为0
/* in case fan is neither on nor off set the fan to active */
if (instance->target != 0 && instance->target != 1) {//bang_bang只有0和1两种状态,0为关闭1为打开,如果非0非1
pr_warn("Thermal instance %s controlled by bang-bang has unexpected state: %ld\n",
instance->name, instance->target);
instance->target = 1;//则为1
}
/*
* enable fan when temperature exceeds trip_temp and disable
* the fan in case it falls below trip_temp minus hysteresis
*/
if (instance->target == 0 && tz->temperature >= trip_temp)//没打开风扇且达到触发温度
instance->target = 1;//则打开风扇
else if (instance->target == 1 &&//打开风扇且温度小于触发温度减去滞后温度
tz->temperature <= trip_temp - trip_hyst)
instance->target = 0;//关闭风扇
dev_dbg(&instance->cdev->device, "target=%d\n",
(int)instance->target);
mutex_lock(&instance->cdev->lock);
instance->cdev->updated = false; /* cdev needs update */
mutex_unlock(&instance->cdev->lock);
}
mutex_unlock(&tz->lock);
}
static int bang_bang_control(struct thermal_zone_device *tz, int trip)//风扇策略
{
struct thermal_instance *instance;
thermal_zone_trip_update(tz, trip);//先更新trip
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);//再更新cdev即cooling device
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor thermal_gov_bang_bang = {
.name = "bang_bang",
.throttle = bang_bang_control,
};
int thermal_gov_bang_bang_register(void)
{
return thermal_register_governor(&thermal_gov_bang_bang);
}
bang_bang两点温度调节或风扇策略,如同风扇一样只有打开和关闭两个状态
2.2.5 power_allocator(drivers\thermal\power_allocator.c)
static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
{
int ret;
int switch_on_temp, control_temp;
struct power_allocator_params *params = tz->governor_data;
/*
* We get called for every trip point but we only need to do
* our calculations once
*/
if (trip != params->trip_max_desired_temperature)
return 0;
ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
&switch_on_temp);//获取trip温度,作为switch_on触发温度,超过此温度governor就会打开
if (!ret && (tz->temperature < switch_on_temp)) {
tz->passive = 0;
reset_pid_controller(params);//将PID控制器的累积误差、上一次的迭代错误写为0
allow_maximum_power(tz);//分配最大可用功耗
return 0;
}
tz->passive = 1;//passive为1表示温度已经达到了被动触发点,其他情况为0
ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,
&control_temp);//获取trip温度,作为目标温度
if (ret) {
dev_warn(&tz->device,
"Failed to get the maximum desired temperature: %d\n",
ret);
return ret;
}
return allocate_power(tz, control_temp);//主要的算法逻辑
}
static struct thermal_governor thermal_gov_power_allocator = {
.name = "power_allocator",
.bind_to_tz = power_allocator_bind,
.unbind_from_tz = power_allocator_unbind,
.throttle = power_allocator_throttle,
};
int thermal_gov_power_allocator_register(void)
{
return thermal_register_governor(&thermal_gov_power_allocator);
}
void thermal_gov_power_allocator_unregister(void)
{
thermal_unregister_governor(&thermal_gov_power_allocator);
static int allocate_power(struct thermal_zone_device *tz,
int control_temp)//IPA主要算法逻辑
{
struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
u32 *req_power, *max_power, *granted_power, *extra_actor_power;
u32 *weighted_req_power;
u32 total_req_power, max_allocatable_power, total_weighted_req_power;
u32 total_granted_power, power_range;
int i, num_actors, total_weight, ret = 0;
int trip_max_desired_temperature = params->trip_max_desired_temperature;//最后被动触发温度
mutex_lock(&tz->lock);
num_actors = 0;
total_weight = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历所有instance
if ((instance->trip == trip_max_desired_temperature) &&
cdev_is_power_actor(instance->cdev)) {//用功耗对cdev进行调整
num_actors++;
total_weight += instance->weight;//统计所有满足条件的actors数量、weight总数
}
}
if (!num_actors) {
ret = -ENODEV;
goto unlock;
}
/*
* We need to allocate five arrays of the same size:
* req_power, max_power, granted_power, extra_actor_power and
* weighted_req_power. They are going to be needed until this
* function returns. Allocate them all in one go to simplify
* the allocation and deallocation logic.
*/
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));//BUILD_BUG_ON(condition)为真则编译报错
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));//此处为保证这4个参数大小一致
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));
req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);//为5个num_actors各申请sizeof(*req_power)大小的内存
if (!req_power) {
ret = -ENOMEM;
goto unlock;
}
max_power = &req_power[num_actors];//获取max_power地址
granted_power = &req_power[2 * num_actors];//获取granted_power地址
extra_actor_power = &req_power[3 * num_actors];//获取extra_actor_power地址
weighted_req_power = &req_power[4 * num_actors];//获取weighted_req_power地址
i = 0;
total_weighted_req_power = 0;
total_req_power = 0;
max_allocatable_power = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历所有instance(cooling devices)
int weight;
struct thermal_cooling_device *cdev = instance->cdev;
if (instance->trip != trip_max_desired_temperature)//找到与最后被动触发相同温度的instance
continue;
if (!cdev_is_power_actor(cdev))//用功耗对cdev进行调整
continue;
if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))//获取需要的功耗保存在max_power里
continue;
if (!total_weight)
weight = 1 << FRAC_BITS;//如果获取的weight总数为0,则weight为20
else
weight = instance->weight;//否则将当前instance的weight赋值给weight
weighted_req_power[i] = frac_to_int(weight * req_power[i]);//获取cooling device的权重功耗(weight*max_power) >> 10
if (power_actor_get_max_power(cdev, tz, &max_power[i]))//获取cdev可用消耗的最大功耗
continue;
total_req_power += req_power[i];//统计上面获取需要的功耗,即总的cooling device需要的功耗
max_allocatable_power += max_power[i];//统计上面获取的可用最大功耗,即总的最大可分配功耗
total_weighted_req_power += weighted_req_power[i];//统计上面获取的(weight*max_power) >> 10,即总的cooling device的权重功耗
i++;
}
power_range = pid_controller(tz, control_temp, max_allocatable_power);//PID控制算法,power_range是当前温度下可支配的最大功耗
divvy_up_power(weighted_req_power, max_power, num_actors,//公摊计算出当前温度下每个cooling device的最终的total_granted_power
total_weighted_req_power, power_range, granted_power,//total_granted_power=granted_power+extra_actor_power
extra_actor_power);
total_granted_power = 0;
i = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历cooling device
if (instance->trip != trip_max_desired_temperature)
continue;
if (!cdev_is_power_actor(instance->cdev))
continue;
power_actor_set_power(instance->cdev, instance,
granted_power[i]);//给cooling device设置granted_power
total_granted_power += granted_power[i];//统计total_granted_power
i++;
}
trace_thermal_power_allocator(tz, req_power, total_req_power,
granted_power, total_granted_power,
num_actors, power_range,
max_allocatable_power, tz->temperature,
control_temp - tz->temperature);
kfree(req_power);
unlock:
mutex_unlock(&tz->lock);
return ret;
}
static int allocate_power(struct thermal_zone_device *tz,
int control_temp)//IPA主要算法逻辑
{
struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
u32 *req_power, *max_power, *granted_power, *extra_actor_power;
u32 *weighted_req_power;
u32 total_req_power, max_allocatable_power, total_weighted_req_power;
u32 total_granted_power, power_range;
int i, num_actors, total_weight, ret = 0;
int trip_max_desired_temperature = params->trip_max_desired_temperature;//最后被动触发温度
mutex_lock(&tz->lock);
num_actors = 0;
total_weight = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历所有instance
if ((instance->trip == trip_max_desired_temperature) &&
cdev_is_power_actor(instance->cdev)) {//用功耗对cdev进行调整
num_actors++;
total_weight += instance->weight;//统计所有满足条件的actors数量、weight总数
}
}
if (!num_actors) {
ret = -ENODEV;
goto unlock;
}
/*
* We need to allocate five arrays of the same size:
* req_power, max_power, granted_power, extra_actor_power and
* weighted_req_power. They are going to be needed until this
* function returns. Allocate them all in one go to simplify
* the allocation and deallocation logic.
*/
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));//BUILD_BUG_ON(condition)为真则编译报错
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));//此处为保证这4个参数大小一致
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));
req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);//为5个num_actors各申请sizeof(*req_power)大小的内存
if (!req_power) {
ret = -ENOMEM;
goto unlock;
}
max_power = &req_power[num_actors];//获取max_power地址
granted_power = &req_power[2 * num_actors];//获取granted_power地址
extra_actor_power = &req_power[3 * num_actors];//获取extra_actor_power地址
weighted_req_power = &req_power[4 * num_actors];//获取weighted_req_power地址
i = 0;
total_weighted_req_power = 0;
total_req_power = 0;
max_allocatable_power = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历所有instance(cooling devices)
int weight;
struct thermal_cooling_device *cdev = instance->cdev;
if (instance->trip != trip_max_desired_temperature)//找到与最后被动触发相同温度的instance
continue;
if (!cdev_is_power_actor(cdev))//用功耗对cdev进行调整
continue;
if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))//获取需要的功耗保存在max_power里
continue;
if (!total_weight)
weight = 1 << FRAC_BITS;//如果获取的weight总数为0,则weight为1 << 10
else
weight = instance->weight;//否则将当前instance的weight赋值给weight
weighted_req_power[i] = frac_to_int(weight * req_power[i]);//获取cooling device的权重功耗(weight*max_power) >> 10
if (power_actor_get_max_power(cdev, tz, &max_power[i]))//获取cdev可用消耗的最大功耗
continue;
total_req_power += req_power[i];//统计上面获取需要的功耗,即总的cooling device需要的功耗
max_allocatable_power += max_power[i];//统计上面获取的可用最大功耗,即总的最大可分配功耗
total_weighted_req_power += weighted_req_power[i];//统计上面获取的(weight*max_power) >> 10,即总的cooling device的权重功耗
i++;
}
power_range = pid_controller(tz, control_temp, max_allocatable_power);//PID控制算法,power_range是当前温度下可支配的最大功耗
divvy_up_power(weighted_req_power, max_power, num_actors,//公摊计算出当前温度下每个cooling device的最终的total_granted_power
total_weighted_req_power, power_range, granted_power,//total_granted_power=granted_power+extra_actor_power
extra_actor_power);
total_granted_power = 0;
i = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {//遍历cooling device
if (instance->trip != trip_max_desired_temperature)
continue;
if (!cdev_is_power_actor(instance->cdev))
continue;
power_actor_set_power(instance->cdev, instance,
granted_power[i]);//给cooling device设置granted_power
total_granted_power += granted_power[i];//统计total_granted_power
i++;
}
trace_thermal_power_allocator(tz, req_power, total_req_power,
granted_power, total_granted_power,
num_actors, power_range,
max_allocatable_power, tz->temperature,
control_temp - tz->temperature);
kfree(req_power);
unlock:
mutex_unlock(&tz->lock);
return ret;
}
static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
u32 total_req_power, u32 power_range,
u32 *granted_power, u32 *extra_actor_power)//公摊计算出当前温度下cooling device最终total_granted_power
{
u32 extra_power, capped_extra_power;
int i;
/*
* Prevent division by 0 if none of the actors request power.
*/
if (!total_req_power)//防止为0
total_req_power = 1;
capped_extra_power = 0;
extra_power = 0;
for (i = 0; i < num_actors; i++) {//遍历数组
u64 req_range = (u64)req_power[i] * power_range;
granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,
total_req_power);//公式:四舍五入power_range * (weighted_req_power[i] / total_weighted_req_power)
if (granted_power[i] > max_power[i]) {//分配的功耗不能大于最大功耗
extra_power += granted_power[i] - max_power[i];//累加分配过多的功耗
granted_power[i] = max_power[i];
}
extra_actor_power[i] = max_power[i] - granted_power[i];
capped_extra_power += extra_actor_power[i];//记录每个申请者分配过多的功耗
}
if (!extra_power)//如果没有分配过多的功耗说明此时分配ok
return;//否则return
/*
* Re-divvy the reclaimed extra among actors based on
* how far they are from the max
*/
extra_power = min(extra_power, capped_extra_power);//重新分配额外功耗
if (capped_extra_power > 0)
for (i = 0; i < num_actors; i++)
granted_power[i] += (extra_actor_power[i] *
extra_power) / capped_extra_power;
}
此策略会获取switch_on温度作为此governor开关的判断,获取target温度,作为目标温度,策略的目的就是让温度维持在目标温度;首先得到此温度下能分配的最大功耗,计算每个cooling device的weight权重
通过PID相加得到可分配的功耗,按照每个cooling device的权重分配
P: p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err);//control_temp大于目标温度使用k_pu,否则使用k_po
//k_po = int_to_frac(sustainable_power / (control_temp - switch_on,k_pu = int_to_frac(2*sustainable_power / (control_temp - switch_on)
I: i = mul_frac(tz->tzp->k_i, params->err_integral);//计算积分项 ,公式:K_i*err_integral(差值的累加),默认:K_i = int_to_frac(10 / 1000)
D: d = mul_frac(tz->tzp->k_d, err - params->prev_err);//计算微分项,公式:K_d*(err - prev_err) / passive_delay,默认:K_d = 0
然后判断分配的功耗与可分配的最大功耗之前是否有差异,如果大于可分配的最大功耗就统计多出来的部分,再按照权重进行分配一次,所以每个cooling device可分配到的功耗就为
Pgranted_i + Pextra_granted_i,这个策略保证在目标温度下能极可能分配多的功耗,保证了性能和温升的需求
2.2.6 user_space
static int notify_user_space(struct thermal_zone_device *tz, int trip)
{
char *thermal_prop[5];
int i;
mutex_lock(&tz->lock);//上锁
thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type);
thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature);
thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip);
thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event);
thermal_prop[4] = NULL;
kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop);//将上述参数发送出去
for (i = 0; i < 4; ++i)
kfree(thermal_prop[i]);//释放指针
mutex_unlock(&tz->lock);//解锁
return 0;
}
static struct thermal_governor thermal_gov_user_space = {
.name = "user_space",
.throttle = notify_user_space,
};
int thermal_gov_user_space_register(void)
{
return thermal_register_governor(&thermal_gov_user_space);
}
user_space策略主要是将温控相关参数传给用户空间,由用户空间指定对应策略
2.3 class_register
调用的是__class_register
#define class_register(class) \
({ \
static struct lock_class_key __key; \
__class_register(class, &__key); \
})
int __class_register(struct class *cls, struct lock_class_key *key)//cls指向"thermal"
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);//申请内存
if (!cp)//内存申请失败
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);//初始化klist
INIT_LIST_HEAD(&cp->interfaces);//初始化interfaces链表
kset_init(&cp->glue_dirs);//初始化kset数据结构
__mutex_init(&cp->mutex, "subsys mutex", key);//初始化互斥体
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);//给内核对象kobject设置名字,相当于在/sys/class目录下创建一个名称为cls->name的文件夹
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;//默认sys/dev路径
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset;
#else
cp->subsys.kobj.kset = class_kset;
#endif
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
error = kset_register(&cp->subsys);//初始化并添加一个kset
if (error) {
kfree(cp);
return error;
}
error = class_add_groups(class_get(cls), cls->class_groups);//给定目录kobject,创建对应属性组
class_put(cls);//递减kobj内核对象的引用计数 当为0的时候调用在kobject_init()中传入的kobj_type{}结构中包含的kobj释放函数
if (error) {
kobject_del(&cp->subsys.kobj);//取消链接object
kfree_const(cp->subsys.kobj.name);//释放内存
kfree(cp);//释放指针
}
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
{//在C中,当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
va_list vargs;
int retval;
va_start(vargs, fmt);//Va_start相关宏获取省略号指定的参数
retval = kobject_set_name_vargs(kobj, fmt, vargs);
va_end(vargs);//释放指针
return retval;
}
EXPORT_SYMBOL(kobject_set_name);
2.4 of_parse_thermal_zones
int __init of_parse_thermal_zones(void)//解析dtsi中&thermal_zones并注册thermal_zone_device
{
struct device_node *np, *child;
struct __thermal_zone *tz;
struct thermal_zone_device_ops *ops;
np = of_find_node_by_name(NULL, "thermal-zones");//通过名称thermal-zones查找节点
//thermal_zones下每一个子节点都是一个thermal,在/sys/class/thermal下对应一个thermal_zoneX目录,ntc的也在里面
if (!np) {
pr_debug("unable to find thermal zones\n");
return 0; /* Run successfully on systems without thermal DT */ //即便dts里没有thermal-zones结点也允许thermal core跑起来
}
for_each_available_child_of_node(np, child) {//遍历所有子节点
struct thermal_zone_device *zone;
struct thermal_zone_params *tzp;
int i, mask = 0;
u32 prop;
tz = thermal_of_build_thermal_zone(child);//创建一个thermal zone节点
if (IS_ERR(tz)) {
pr_err("failed to build thermal zone %s: %ld\n",
child->name,
PTR_ERR(tz));
continue;
}
ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);//新申请一段内存,将of_thermal_ops内容复制到新申请的内存中
if (!ops)
goto exit_free;
tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);//申请内存
if (!tzp) {
kfree(ops);
goto exit_free;
}
/* No hwmon because there might be hwmon drivers registering */
tzp->no_hwmon = true;//不创建hwmon
if (!of_property_read_u32(child, "sustainable-power", &prop))//解析sustainable-power字段
tzp->sustainable_power = prop;//读出来写到sustainable_power中
for (i = 0; i < tz->ntrips; i++)
mask |= 1 << i;
/* these two are left for temperature drivers to use */
tzp->slope = tz->slope;//温度调整曲线的斜率
tzp->offset = tz->offset;//温度调整曲线的偏移量
zone = thermal_zone_device_register(child->name, tz->ntrips,//向thermal_core注册thermal_zone_devices并绑定cool device
mask, tz,
ops, tzp,
tz->passive_delay,
tz->polling_delay);
if (IS_ERR(zone)) {
pr_err("Failed to build %s zone %ld\n", child->name,
PTR_ERR(zone));
kfree(tzp);
kfree(ops);
of_thermal_free_zone(tz);
/* attempting to build remaining zones still */
}
}
of_node_put(np);
return 0;
exit_free:
of_node_put(child);
of_node_put(np);
of_thermal_free_zone(tz);
/* no memory available, so free what we have built */
of_thermal_destroy_zones();
return -ENOMEM;
}
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name)
{
struct device_node *np;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);//上锁
for_each_of_allnodes_from(from, np)
if (np->name && (of_node_cmp(np->name, name) == 0)//按name查找节点
&& of_node_get(np))//增加节点的引用计数
break;
of_node_put(from);//减少节点的引用计数
raw_spin_unlock_irqrestore(&devtree_lock, flags);//解锁
return np;
}
EXPORT_SYMBOL(of_find_node_by_name);
static struct __thermal_zone
__init *thermal_of_build_thermal_zone(struct device_node *np)//创建一个thermal_zone节点
{
struct device_node *child = NULL, *gchild;
struct __thermal_zone *tz;
int ret, i;
u32 prop, coef[2];
if (!np) {
pr_err("no thermal zone np\n");
return ERR_PTR(-EINVAL);
}
tz = kzalloc(sizeof(*tz), GFP_KERNEL);//申请内存
if (!tz)
return ERR_PTR(-ENOMEM);
ret = of_property_read_u32(np, "polling-delay-passive", &prop);//解析polling-delay-passive,超过阀值轮询时间
//读取np结点中的propname属性的值,并将读取到的u32类型的值保存在out_value指向的内存中,函数的返回值表示读取到的u32类型的数据的个数
if (ret < 0) {
pr_err("missing polling-delay-passive property\n");
goto free_tz;
}
tz->passive_delay = prop;
ret = of_property_read_u32(np, "polling-delay", &prop);//解析polling-delay,未超过阀值轮询时间
if (ret < 0) {
pr_err("missing polling-delay property\n");
goto free_tz;
}
tz->polling_delay = prop;
/*
* REVIST: for now, the thermal framework supports only
* one sensor per thermal zone. Thus, we are considering
* only the first two values as slope and offset.
*/
ret = of_property_read_u32_array(np, "coefficients", coef, 2);//读取属性中u32类型的数组数据,解析coefficients
if (ret == 0) {
tz->slope = coef[0];//slope斜率
tz->offset = coef[1];//offset偏移量
} else {
tz->slope = 1;
tz->offset = 0;
}
/* trips */
child = of_get_child_by_name(np, "trips");//查找trips字段
/* No trips provided */
if (!child)
goto finish;
tz->ntrips = of_get_child_count(child);//获取子节点数量
if (tz->ntrips == 0) /* must have at least one child */
goto finish;
tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL);//申请内存
if (!tz->trips) {
ret = -ENOMEM;
goto free_tz;
}
i = 0;
for_each_child_of_node(child, gchild) {
ret = thermal_of_populate_trip(gchild, &tz->trips[i++]);//遍历解析trip字段下面的字段
if (ret)
goto free_trips;
}
of_node_put(child);//减少节点计数
/* cooling-maps */
child = of_get_child_by_name(np, "cooling-maps");//查找cooling-maps字段
/* cooling-maps not provided */
if (!child)
goto finish;
tz->num_tbps = of_get_child_count(child);
if (tz->num_tbps == 0)
goto finish;
tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL);
if (!tz->tbps) {
ret = -ENOMEM;
goto free_trips;
}
i = 0;
for_each_child_of_node(child, gchild) {//遍历解析cooling-maps下面字段,绑定cooling device
ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++],
tz->trips, tz->ntrips);
if (ret)
goto free_tbps;
}
finish:
of_node_put(child);
tz->mode = THERMAL_DEVICE_DISABLED;
return tz;
free_tbps:
for (i = i - 1; i >= 0; i--)
of_node_put(tz->tbps[i].cooling_device);
kfree(tz->tbps);
free_trips:
for (i = 0; i < tz->ntrips; i++)
of_node_put(tz->trips[i].np);
kfree(tz->trips);
of_node_put(gchild);
free_tz:
kfree(tz);
of_node_put(child);
return ERR_PTR(ret);
}
struct thermal_zone_device *
thermal_zone_device_register(const char *type, int trips, int mask,
void *devdata, struct thermal_zone_device_ops *ops,
struct thermal_zone_params *tzp, int passive_delay,
int polling_delay)//注册一个thermal_zone_device
{
struct thermal_zone_device *tz;
enum thermal_trip_type trip_type;
int trip_temp;
int result;
int count;
struct thermal_governor *governor;
if (!type || strlen(type) == 0)
return ERR_PTR(-EINVAL);
if (type && strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL);
if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)
return ERR_PTR(-EINVAL);
if (!ops)
return ERR_PTR(-EINVAL);
if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
return ERR_PTR(-EINVAL);
tz = kzalloc(sizeof(*tz), GFP_KERNEL);
if (!tz)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&tz->thermal_instances);//初始化一个链表thermal_instances
ida_init(&tz->ida);//初始化冷却设备唯一ID
mutex_init(&tz->lock);//初始化互斥锁
result = ida_simple_get(&thermal_tz_ida, 0, 0, GFP_KERNEL);//自动分配id
if (result < 0)
goto free_tz;
tz->id = result;
strlcpy(tz->type, type, sizeof(tz->type));
tz->ops = ops;
tz->tzp = tzp;
tz->device.class = &thermal_class;
tz->devdata = devdata;
tz->trips = trips;
tz->passive_delay = passive_delay;
tz->polling_delay = polling_delay;
/* sys I/F */
/* Add nodes that are always present via .groups */
result = thermal_zone_create_device_groups(tz, mask);//添加节点
if (result)
goto remove_id;
/* A new thermal zone needs to be updated anyway. */
atomic_set(&tz->need_update, 1);//设置原子变量的值
dev_set_name(&tz->device, "thermal_zone%d", tz->id);//设置thermal_zone节点名称
result = device_register(&tz->device);//注册设备
if (result)
goto remove_device_groups;
for (count = 0; count < trips; count++) {
if (tz->ops->get_trip_type(tz, count, &trip_type))//获取trip类型
set_bit(count, &tz->trips_disabled);
if (tz->ops->get_trip_temp(tz, count, &trip_temp))//获取trip温度
set_bit(count, &tz->trips_disabled);//将tz->trips_disabled第count位设置为1
/* Check for bogus trip points */
if (trip_temp == 0)
set_bit(count, &tz->trips_disabled);
}
/* Update 'this' zone's governor information */
mutex_lock(&thermal_governor_lock);
if (tz->tzp)
governor = __find_governor(tz->tzp->governor_name);//thermal_zone设置governor,否则默认governor
else
governor = def_governor;
result = thermal_set_governor(tz, governor);//切换新的governor
if (result) {
mutex_unlock(&thermal_governor_lock);
goto unregister;
}
mutex_unlock(&thermal_governor_lock);
if (!tz->tzp || !tz->tzp->no_hwmon) {
result = thermal_add_hwmon_sysfs(tz);//判断是否需要添加hwmon,如是则添加上
if (result)
goto unregister;
}
mutex_lock(&thermal_list_lock);
list_add_tail(&tz->node, &thermal_tz_list);//将thermal zone加入到thermal_tz_list
mutex_unlock(&thermal_list_lock);
/* Bind cooling devices for this zone */
bind_tz(tz);//将thermal_cdev_list上的cooling设备绑定到thermal_zone_device上
INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);//初始化work quene下半部分,处理中断需要响应的操作,定时去调用thermal_zone_device_update
//设置polling_delay值为轮询周期
thermal_zone_device_reset(tz);//对thermal zone的温度等复位
/* Update the new thermal zone and mark it as already updated. */
if (atomic_cmpxchg(&tz->need_update, 1, 0))
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
return tz;
unregister:
ida_simple_remove(&thermal_tz_ida, tz->id);
device_unregister(&tz->device);
return ERR_PTR(result);
remove_device_groups:
thermal_zone_destroy_device_groups(tz);
remove_id:
ida_simple_remove(&thermal_tz_ida, tz->id);
free_tz:
kfree(tz);
return ERR_PTR(result);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_register);
从上面代码中可以看出解析dts就是匹配对应字段,然后复制给结构体对应成员
下面给出dts配置中字段的含义
thermal dts配置说明
thermal_zones: thermal-zones {//一个节点对应一个thermal zone,并包含温控策略相关参数
soc_thermal: soc-thermal {
polling-delay-passive = <20>;//温度高于trip-point-0指定的值即发生温控时的轮询周期,每隔20ms获取一次温度
polling-delay = <1000>;//温度低于trip-point-0指定的值即未发生温控时的轮询周期,每隔1000ms获取一次温度
sustainable-power = <1000>;//温度等于trip-point-1指定的值时,系统分配给cooling device的能量
thermal-sensors = <&tsadc 0>;//当前thermal zone通过tsadc0获取温度,使用的是tsadc的通道0
trips {//trips包含不同温度阈值,不同的温控策略,配置不一定相同
threshold: trip-point-0 {
temperature = <70000>;//超过70摄氏度,温控策略开始工作
hysteresis = <2000>;//温度低于70-2=68度时,温控停止工作
type = "passive";//表示超过该温度值时,使用polling-delay-passive
};
target: trip-point-1 {//温控目标温度,期望通过降频使得芯片不超过该值
temperature = <85000>;//期望通过降频使得芯片不超过85摄氏度
hysteresis = <2000>;//
type = "passive";//表示超过该温度值时,使用polling-delay-passive
};
soc_crit: soc-crit {//过温保护阈值,如果降频后温度仍然上升,那么超过该值后,让系统重启
temperature = <115000>;//超过115摄氏度重启
hysteresis = <2000>;
type = "critical";//表示超过该温度值时,重启
};
};
cooling-maps {//cooling device配置节点,每个子节点代表一个cooling device
map0 {
trip = <&target>;//表示在target trip下,该cooling device才起作用,对于power allocater策略必须填target
cooling-device = <&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;//对应的真正执行冷却操作的设备及最大/最小状态,THERMAL_NO_LIMIT不起作用
contribution = <4096>;//计算功耗时乘以4096/1024倍,用于调整降频顺序和尺度
};
map1 {
trip = <&target>;
cooling-device = <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
contribution = <1024>;
};
map2 {
trip = <&target>;
cooling-device = <&gpu THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
contribution = <4096>;
};
};
};
gpu_thermal: gpu-thermal {//一个节点对应一个thermal zone,并包含温控策略相关参数,当前thermal zone只用于获取温度
polling-delay-passive = <100>; /* milliseconds */
polling-delay = <1000>; /* milliseconds */
thermal-sensors = <&tsadc 1>;
};
};
参考:万字长文 | Thermal框架源码剖析
Linux Thermal机制源码分析之Thermal zone_linux thermal zone_不捡风筝的玖伍贰柒的博客-CSDN博客
版权声明:本文标题:Linux thermal简述 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1726927404a1090700.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论