linux thermal framework(3)_thermal cooling device
原文:蜗窝科技linux thermal framework(3)_thermal cooling device
1. 介绍
linux使用thermal cooling device来描述一个平台上可以降温的设备。
2. thermal cooling device相关的API以及功能分析
一个thermal cooling device是用struct thermal_cooling_device来表示的
truct thermal_cooling_device {
//1)id,同样的,每个cooling device都有一个id作为区分,这个id也是在注册的时候通过ida分配
int id;
//2)type,用来说明描述的哪一种类型的cooling device,一般这个type会有cpufreq/devfreq等
char *type;
//3)max_state,cooling device会有若干个state,governor通过不断的调整这些state来控制温度
unsigned long max_state;
//4)device,cooling device也是一个抽象出来的设备,因此有一个device数据结构
struct device device;
struct device_node *np;
void *devdata;
void *stats;
//5)ops,这个cooling device的回调函数
const struct thermal_cooling_device_ops *ops;
bool updated; /* true if the cooling device does not need update */
struct mutex lock; /* protect thermal_instances list */
struct list_head thermal_instances;
struct list_head node;
ANDROID_KABI_RESERVE(1);
};
max_state,这个cooling_device最大state的索引
t hermal_instances, 这个节点和struct thermal_instance有关,我们之前说过,对thermal zone的某一个passive or active类型的trip,会有cooling device的策略介入,那么struct thermal_instance就是作为连接trip和相应的cooling device的,struct thermal_instance的定义如下:
alps/kernel-6.6/drivers/thermal/thermal_core.h
/*
* This structure is used to describe the behavior of
* a certain cooling device on a certain trip point
* in a certain thermal zone
* thermal_instance 用于描述特定散热设备在特定温控区域(thermal zone)
* 的特定触发点(trip point)上的行为表现
*/
struct thermal_instance {
/* 标识符 */
int id; // 实例ID,唯一标识该thermal_instance
char name[THERMAL_NAME_LENGTH]; // 实例名称,用于调试和日志记录
/* 关联设备 */
struct thermal_zone_device *tz; // 所属温控区域设备指针
struct thermal_cooling_device *cdev; // 关联的散热设备指针
const struct thermal_trip *trip; // 关联的温度触发点指针
/* 状态标志 */
bool initialized; // 初始化状态标记
/* 散热状态范围控制 */
unsigned long upper; // 该触发点允许的最高散热状态(冷却等级)
unsigned long lower; // 该触发点允许的最低散热状态
unsigned long target; // 当前目标散热状态(期望值)
/* sysfs 接口 */
char attr_name[THERMAL_NAME_LENGTH]; // 属性文件名(通常以trip ID命名)
struct device_attribute attr; // 设备属性(用于暴露到sysfs)
char weight_attr_name[THERMAL_NAME_LENGTH]; // 权重属性文件名
struct device_attribute weight_attr; // 权重属性(sysfs可调节参数)
/* 链表管理 */
struct list_head tz_node; // 温控区域实例链表节点
struct list_head cdev_node; // 散热设备实例链表节点
/* 权重配置 */
unsigned int weight; // 该散热设备在当前触发点的权重值(0-100)
// 用于多散热设备协同工作时分配散热负载比例
/* 特殊模式 */
bool upper_no_limit; // 是否禁用散热状态上限(允许最大冷却能力)
};
核心功能:
1.实现温控区域(thermal zone)、触发点(trip point)和散热设备(cooling device)的三者关联
2.通过upper/lower/target实现散热状态的闭环控制
关键设计:
1.权重机制(weight):支持多散热设备按比例分配散热任务
2.无上限模式(upper_no_limit):紧急情况下允许散热设备全速运行
我们在下面的章节具体讲一下这个结构体是如何被管理的
2.2 thermal_cooling_device的注册
和thermal zone一样,cooling device也不是一个实际存在的物理设备,是由linux抽象出来表示一类可降温的设备,这些设备可以有cpufreq(通过限制cpu频率来降低cpu温度)、devfreq(通过限制设备频率来降低设备温度)、cpuidle(通过进入低功耗模式的idle来降低cpu温度),同样的,cooling device也是通过cpufreq/devfreq/cpuidle这些设备注册的时候,向thermal core注册的。我们以cpufreq为例来描述详细的注册过程。
和thermal zone一样,我们也通过dts中一个实际的例子来描述cooling device注册,这里再次使用mt8195.dtsi中的cpu作为例子,以下是cpu0的thermal_zone节点,只保留了cooling-map相关的信息:
/kernel/kernel-6.6/arch/arm64/boot/dts/mediatek/mt8195.dtsi
thermal_zones: thermal-zones { // 定义所有温控区域(thermal zones)的父节点
cpu0-thermal { // 定义CPU0温控区域
polling-delay = <1000>; // 主动轮询间隔(单位:毫秒),当温度超过触发点时使用
polling-delay-passive = <250>; // 被动轮询间隔(单位:毫秒),用于温度接近触发点时的监控
// 指定该温控区域使用的温度传感器
// &lvts_mcu: 引用之前定义的LVTS传感器节点
// MT8195_MCU_LITTLE_CPU0: 传递给传感器的参数,标识具体监控的CPU核心
thermal-sensors = <&lvts_mcu MT8195_MCU_LITTLE_CPU0>;
// 定义温度触发点(trip points)
trips {
// 警告级触发点(被动冷却)
cpu0_alert: trip-alert {
temperature = <85000>; // 触发温度(单位:毫摄氏度,即85°C)
hysteresis = <2000>; // 滞后温度(2°C),避免温度在临界点附近频繁切换状态
type = "passive"; // 触发类型:被动冷却(通过降频等方式降温)
};
// 临界级触发点(紧急处理)
cpu0_crit: trip-crit {
temperature = <100000>; // 临界温度(100°C)
hysteresis = <2000>; // 滞后温度(2°C)
type = "critical"; // 触发类型:临界状态(可能触发硬件保护或系统关机)
};
};
// 定义冷却设备映射关系
cooling-maps {
// 映射0:关联触发点和冷却设备
map0 {
trip = <&cpu0_alert>; // 引用上述定义的警告级触发点
// 指定冷却设备及其操作范围
cooling-device =
<&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, // CPU0冷却设备,无限制范围
<&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, // CPU1冷却设备
<&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, // CPU2冷却设备
<&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; // CPU3冷却设备
// THERMAL_NO_LIMIT表示不限制最大/最小冷却状态
};
};
};
};
cpu0-thermal只有一个cooling-map,即map0,map0对应的trip是类型为passive的trip-alert,他的cooling-device引用了四个cpu,我们看下相同dts中的cpu0的节点描述:
cpus {
/*
* 定义CPU节点的寻址方式:
* - address-cells = 1 表示reg属性用1个u32表示地址
* - size-cells = 0 表示reg属性不包含大小字段
*/
#address-cells = <1>;
#size-cells = <0>;
/* CPU0 节点定义 */
cpu0: cpu@0 {
/* 基础属性 */
device_type = "cpu"; // 设备类型标识
compatible = "arm,cortex-a55"; // 兼容的CPU架构
reg = <0x000>; // 物理地址编码(此处为0号CPU)
/* 电源管理 */
enable-method = "psci"; // 使用ARM PSCI协议启动CPU
performance-domains = <&performance 0>; // 关联的性能域(用于DVFS调频)
clock-frequency = <1701000000>; // 标称主频(1.701GHz)
capacity-dmips-mhz = <308>; // CPU算力指标(DMIPS/MHz)
cpu-idle-states = <&cpu_ret_l &cpu_off_l>; // 支持的休眠状态(retention/off)
/* 缓存配置 */
i-cache-size = <32768>; // 指令缓存大小(32KB)
i-cache-line-size = <64>; // 指令缓存行大小(64B)
i-cache-sets = <128>; // 指令缓存组数
d-cache-size = <32768>; // 数据缓存大小(32KB)
d-cache-line-size = <64>; // 数据缓存行大小(64B)
d-cache-sets = <128>; // 数据缓存组数
next-level-cache = <&l2_0>; // 二级缓存引用
/* 温控接口 */
#cooling-cells = <2>; // 定义该CPU可作为冷却设备
// 参数2表示需要提供<冷却类型 等级>两个参数
};
};
cpu0使用cooling-cells来描述使用几个u32来引用这个cooling device。
cpu的cooling device是在cpufreq policy初始化的时候通过调用of_cpufreq_cooling_register向thermal core注册的,这个函数会判断dts中是否有cooling-cells节点,如果存在的话,会调用__cpufreq_cooling_register
/**
* thermal_bind_cdev_to_trip - 将散热设备绑定到温控区域的触发点
* @tz: 目标温控区域设备
* @td: 要绑定的温度触发点描述符
* @cdev: 要绑定的散热设备
* @cool_spec: 散热规格(包含上下限和权重)
*
* 核心功能:
* 1. 建立温控区域(tz)、触发点(trip)和散热设备(cdev)的关联关系
* 2. 创建sysfs控制接口(触发点状态/权重调节)
* 3. 初始化散热控制参数(工作范围、权重等)
*
* 典型调用场景:
* - 在温控区域的.bind回调中调用
* - 系统初始化时构建散热拓扑
*
* 返回值:0成功,负值表示错误码
*/
static int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz,
struct thermal_trip_desc *td,
struct thermal_cooling_device *cdev,
struct cooling_spec *cool_spec)
{
struct thermal_instance *dev;
bool upper_no_limit;
int result;
/* 参数校验与默认值处理 */
if (cool_spec->lower == THERMAL_NO_LIMIT)
cool_spec->lower = 0; // 下限默认为0(最低散热等级)
if (cool_spec->upper == THERMAL_NO_LIMIT) {
cool_spec->upper = cdev->max_state; // 上限默认为设备最大能力
upper_no_limit = true; // 标记无上限限制
} else {
upper_no_limit = false;
}
/* 有效性检查:下限≤上限≤设备最大能力 */
if (cool_spec->lower > cool_spec->upper || cool_spec->upper > cdev->max_state)
return -EINVAL;
/* 分配thermal_instance内存 */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* 初始化实例参数 */
dev->cdev = cdev; // 关联散热设备
dev->trip = &td->trip; // 关联温度触发点
dev->upper = cool_spec->upper; // 设置工作上限
dev->upper_no_limit = upper_no_limit; // 记录上限限制模式
dev->lower = cool_spec->lower; // 设置工作下限
dev->target = THERMAL_NO_TARGET; // 初始无目标状态
dev->weight = cool_spec->weight; // 设置散热权重
/* 分配唯一ID并命名 */
result = ida_alloc(&tz->ida, GFP_KERNEL);
if (result < 0)
goto free_mem;
dev->id = result;
sprintf(dev->name, "cdev%d", dev->id);
/* 创建sysfs符号链接(温控区域→散热设备)*/
result = sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
if (result)
goto release_ida;
/* 创建触发点状态文件(只读)*/
snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point", dev->id);
sysfs_attr_init(&dev->attr.attr);
dev->attr.attr.name = dev->attr_name;
dev->attr.attr.mode = 0444; // 权限:只读
dev->attr.show = trip_point_show; // 绑定显示回调
result = device_create_file(&tz->device, &dev->attr);
if (result)
goto remove_symbol_link;
/* 创建权重调节文件(可读写)*/
snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),
"cdev%d_weight", dev->id);
sysfs_attr_init(&dev->weight_attr.attr);
dev->weight_attr.attr.name = dev->weight_attr_name;
dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO; // 权限:用户可读写
dev->weight_attr.show = weight_show; // 绑定显示回调
dev->weight_attr.store = weight_store; // 绑定写入回调
result = device_create_file(&tz->device, &dev->weight_attr);
if (result)
goto remove_trip_file;
/* 将实例加入温控区域和散热设备的关联列表 */
result = thermal_instance_add(dev, cdev, td);
if (result)
goto remove_weight_file;
/* 通知温控策略更新 */
thermal_governor_update_tz(tz, THERMAL_TZ_BIND_CDEV);
return 0;
/* 错误处理流程(逆向释放资源)*/
remove_weight_file:
device_remove_file(&tz->device, &dev->weight_attr);
remove_trip_file:
device_remove_file(&tz->device, &dev->attr);
remove_symbol_link:
sysfs_remove_link(&tz->device.kobj, dev->name);
release_ida:
ida_free(&tz->ida, dev->id);
free_mem:
kfree(dev);
return result;
}
1)注释:这个接口将一个cooling device和一个thermal zone的某个固定的trip进行绑定
2)初始化一个thermal_instance,通过向它属于的thermal zone申请一个id,这个instance就叫做cdev+id,并在这个thermal zone的目录下面创建一个软连接,名字就是instance的名字,指向对应的cooling_device目录
3)初始化这个instance的attr和weight_attr,分别叫做cdev+id_trip_point和cdev+id_weight,并在这个thermal zone的目录下创建对应的文件
4)最后将这个instance分别加入thermal trip desc和cooling device的相应链表中
至此,thermal zone,trip,cooling device之间的关如下图,一个thermal zone包含若干个trip,thermal zone用数组来管理它的trip,一个trip可以对应若干个cooling device,一个cooling device也可以map若干个不同thermal zone的trip,trip和cooling device的对应关系通过thermal instance来描述
一个有两个trip的thermal zone的sysfs目录中trip相关的文件如下: