原文:蜗窝科技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相关的文件如下:
