Android thermal (4)_cooling device(上)
前言
前面介绍过thermal子系统中部分重要结构,本次来到cooling device。在 Linux thermal framework 中,cooling device(冷却设备)是一个 可控的散热/限功耗单元。
它不一定是物理的“风扇”,也可以是 降低 CPU 频率、降低 GPU 频率、关掉 NPU 模块 等。对 thermal framework 来说,它就是一个“温控手段”的抽象。换句话说:sensor 负责测温 → thermal zone 定义温度策略 → cooling device 执行降温动作。
1 cooling device介绍
1.1 cooling device 的作用
(1)连接 温度 和 动作
当 thermal zone 检测温度超过某个 trip point(阈值),它会调用 cooling device 的接口。比如:
- 温度 > 80℃ → CPU 频率降低一个档位(cpufreq cooling device)
- 温度 > 90℃ → GPU 降频(devfreq cooling device)
- 温度 > 95℃ → 打开风扇(fan cooling device)
- 温度 > 100℃ → 强制关机(system shutdown cooling device)
(2)提供 可编程降温手段
- 通过 thermal_cooling_device_ops,内核提供通用接口(get/set state, state2power, power2state)。
- 各个厂商/驱动只需要实现自己的 cooling device,比如 Rockchip TSADC 驱动里 CPU 降频、高通TSENS 里的 GPU 降频,就可以被统一纳入 thermal framework 管理
1.2 cooling device 的分类(常见)
Linux 里已经有一些通用的 cooling device 驱动:
- CPUFreq cooling device:通过 cpufreq 降低 CPU 频率 / 电压。
- Devfreq cooling device:控制 GPU / NPU / DSP 的运行频率。
- Fan cooling device:控制风扇转速(0 档 / 1 档 / 2 档 …)。
- Power actor cooling device:直接限制模块的功率消耗(Watt)。
- System shutdown cooling device:温度过高时,触发系统关机或重启。
1.3 cooling device 的工作机制
整体流程可以这样理解:
- 传感器采样
thermal sensor(如 Rockchip TSADC / Qualcomm TSENS)测得温度。 - thermal zone 判断
thermal zone 定义了 trip points(阈值)。比如 trip0 = 70℃(轻度),trip1 = 90℃(严重)。 - governor 策略决策
governor(如 step_wise, power_allocator)根据温度,决定需要激活哪个 cooling device、到什么 level。 - 调用 cooling device ops
thermal core 调用 set_cur_state() 或 power2state() 来实际执行操作。 - 执行降温动作
风扇加速、CPU/GPU 降频、触发关机 …
1.4 结构关系
- Thermal Sensor:负责实时采集温度(如 TSADC/TSENS)。
- Thermal Zone:热管理核心,维护 trip point、governor 策略。
- Cooling Device:执行降温动作(CPU 降频、风扇、关机等)
thermal sensor ↔ thermal zone ↔ cooling device 在 Linux Thermal Framework 中的关系用时序/交互图:
2 结构体&dtsi
2.1 thermal_cooling_device
这个结构体表示 一个冷却设备 实例(device object)。
struct thermal_cooling_device {int id; // cooling device 的全局唯一 IDchar *type; // cooling device 类型(字符串),例如 "processor", "gpu", "fan"unsigned long max_state; // 冷却设备的最大状态值 (0 ~ max_state)struct device device; // 对应的内核 device 结构,挂到设备模型struct device_node *np; // 设备树中的节点指针(如果有的话)void *devdata; // 私有数据,驱动注册时传入void *stats; // 统计数据(比如 cooling device 的使用情况)const struct thermal_cooling_device_ops *ops; // 操作函数表 (回调接口)bool updated; // cooling device 是否需要更新struct mutex lock; // 保护 thermal_instances 链表struct list_head thermal_instances; // 挂载在此 cooling device 上的 thermal_instance 列表struct list_head node; // cooling device 全局链表节点(thermal_cdev_list)ANDROID_KABI_RESERVE(1); // Android 内核兼容性保留字段
};
2.2 thermal_cooling_device_ops
这是 冷却设备驱动 的一组回调函数(ops),用于告诉 thermal framework 如何操作该 cooling device。
struct thermal_cooling_device_ops {int (*get_max_state) (struct thermal_cooling_device *, unsigned long *);int (*get_cur_state) (struct thermal_cooling_device *, unsigned long *);int (*set_cur_state) (struct thermal_cooling_device *, unsigned long);int (*get_requested_power)(struct thermal_cooling_device *, u32 *);int (*state2power)(struct thermal_cooling_device *, unsigned long, u32 *);int (*power2state)(struct thermal_cooling_device *, u32, unsigned long *);
};
详细说明如下:
-
get_max_state(struct thermal_cooling_device *, unsigned long *)
返回该冷却设备的 最大冷却状态(多少个等级)。比如风扇有 0–3 档,那最大 state = 3 -
get_cur_state(struct thermal_cooling_device *, unsigned long *)
获取当前冷却设备的状态。比如风扇目前在 2 档,GPU 降频到一半。 -
set_cur_state(struct thermal_cooling_device *, unsigned long)
设置冷却设备的状态。这是 thermal framework 调用 cooling device 实际动作的入口。比如当温度超过 trip point,就调用这个函数把风扇调到高档,或者降低 CPU 频率。 -
get_requested_power(…)
可以获取当前冷却设备能够消耗/限制的功率。适用于 power actor 模式(动态功率限制)。 -
state2power(…)
将冷却设备的 “state” 映射到实际功率限制。例如 CPU 降频到 state=2,功耗从 10W → 6W。 -
power2state(…)
功率到状态的映射。Thermal governor 可以直接基于功率约束来决定需要的冷却 state。
【重要】简单总结:
* xxx_state 系列是 状态 控制接口(风扇转速、CPU 降频级别)。
* xxx_power 系列是 功率 控制接口(高端芯片常见,支持智能功率管理)。
3 关键函数
继续thermal zone之后的调用,thermal zone在注册过程,调用 thermal_of_for_each_cooling_maps、thermal_zone_bind_cooling_device等函数,关联到绑定 thermal zone 与cooling device。
2.1 thermal_of_for_each_cooling_maps
thermal_of_for_each_cooling_maps 遍历一个 thermal_zone_device 的 所有 cooling-maps 节点,并对每个 cooling map 调用 action 函数(比如 bind/unbind)。
static int thermal_of_for_each_cooling_maps(struct thermal_zone_device *tz,struct thermal_cooling_device *cdev,int (*action)(struct device_node *, int, int,struct thermal_zone_device *, struct thermal_cooling_device *))
{struct device_node *tz_np, *cm_np, *child;int ret = 0;// 根据 thermal zone 的名字找到对应的 Device Tree 节点。tz_np = thermal_of_zone_get_by_name(tz);if (IS_ERR(tz_np)) {pr_err("Failed to get node tz by name\n");return PTR_ERR(tz_np);}// 找 thermal zone 节点下的 cooling-maps 子节点cm_np = of_get_child_by_name(tz_np, "cooling-maps");if (!cm_np)goto out;// 遍历 cooling-maps 下的每个子节点,for_each_child_of_node是一个循环遍历的宏定义for_each_child_of_node(cm_np, child) {// 对每一个 map 节点,调用 thermal_of_for_each_cooling_deviceret = thermal_of_for_each_cooling_device(tz_np, child, tz, cdev, action);if (ret) {of_node_put(child);break;}}// 最后释放引用计数:of_node_put(cm_np); of_node_put(tz_np);of_node_put(cm_np);
out:of_node_put(tz_np);return ret;
}
3.2 thermal_of_for_each_cooling_device
thermal_of_for_each_cooling_device 在**一个 map_np(某个 cooling-map 节点)**中,找到它引用的 trip 点和 cooling-device 列表,然后调用 action。
action 是一个指向目标函数的指针,通过action封装。在之前的调用关系,action等通过bind 或 unbind操作,具体需要看调用位置。
static int thermal_of_for_each_cooling_device(struct device_node *tz_np, struct device_node *map_np,struct thermal_zone_device *tz, struct thermal_cooling_device *cdev,int (*action)(struct device_node *, int, int,struct thermal_zone_device *, struct thermal_cooling_device *))
{struct device_node *tr_np;int count, i, trip_id;// 从 map_np 读取 trip 属性(即 thermal trip 节点的 phandle)。tr_np = of_parse_phandle(map_np, "trip", 0);if (!tr_np)return -ENODEV;// 根据 trip 节点,找到对应的 trip id。trip_id = of_find_trip_id(tz_np, tr_np);if (trip_id < 0)return trip_id;// 统计 cooling-device 属性里有多少个 cooling device 被引用count = of_count_phandle_with_args(map_np, "cooling-device", "#cooling-cells");// 如果 ≤0,说明 DT 有问题(至少要有一个 cooling device)。if (count <= 0) {pr_err("Add a cooling_device property with at least one device\n");return -ENOENT;}/** At this point, we don't want to bail out when there is an* error, we will try to bind/unbind as many as possible* cooling devices*/// 遍历每个 cooling devicefor (i = 0; i < count; i++)// 对 map_np 里的每一个 cooling device 调用 action(即 bind 或 unbind)。action(map_np, i, trip_id, tz, cdev);return 0;
}
例如,当 thermal_of_unbind(tz, cdev) 调用时:
- 它会传入 __thermal_of_unbind 作为 action。
- thermal_of_for_each_cooling_maps 遍历 tz 下所有 cooling-maps
- thermal_of_for_each_cooling_device 遍历 cooling-maps 里的每个 cooling-device。
- 对于每个 cooling-device,调用 __thermal_of_unbind(…),去检查它是否是当前要 unbind 的 cdev,如果是,则调用 thermal_zone_unbind_cooling_device。
函数 (thermal_of_for_each_cooling_maps + thermal_of_for_each_cooling_device) 就是 thermal 框架里的 迭代器模式,专门遍历 DT 里定义的 cooling-maps,并对每个 (trip, cooling-device) 绑定关系执行 action(bind/unbind)。也就是说:
- thermal_of_for_each_cooling_maps = 遍历 所有 map 节点
- thermal_of_for_each_cooling_device = 遍历 map 节点里的所有 cooling device
- action = 具体操作(bind/unbind)
3.3 thermal_of_zone_get_by_name
thermal_of_zone_get_by_name 的作用就是根据 thermal_zone_device 的名字(tz->type)找到对应的 device tree 节点。
static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_device *tz)
{struct device_node *np, *tz_np;// 1. 找到 dts 里的 "thermal-zones" 根节点np = of_find_node_by_name(NULL, "thermal-zones");if (!np)return ERR_PTR(-ENODEV);// 2. 在 thermal-zones 下面找到名字为 tz->type 的子节点tz_np = of_get_child_by_name(np, tz->type);// 3. 释放对 np 的引用of_node_put(np);// 4. 没找到子节点,就返回错误if (!tz_np)return ERR_PTR(-ENODEV);return tz_np;
}
逻辑解读
(1)找到根节点 “thermal-zones”
- DTS 中通常会有一个类似这样的定义:thermal-zones {…}
- of_find_node_by_name(NULL, “thermal-zones”) 就是找到这个根节点。
(2)找到具体的 thermal zone
- 每个 thermal_zone_device 都有一个 type 字符串,例如 “cpu-thermal”、“gpu-thermal”。
- of_get_child_by_name(np, tz->type) 就是去找对应的 zone 节点。
(3)引用计数管理
- of_node_put(np) 用来释放 thermal-zones 根节点的引用(因为后续我们只需要具体的子节点)。
(4)错误处理
- 如果找不到对应的 zone,就返回 ERR_PTR(-ENODEV)。
(5)返回正确的节点
- 返回的 tz_np 后续会被用来解析 cooling-maps、trips 等属性。
3.4 thermal_zone_bind_cooling_device
thermal_zone_bind_cooling_device() 用来把 一个 cooling device(降温手段) 绑定到 一个 thermal zone(热区) 的 某个 trip point(温度阈值) 上。绑定完成后,governor 在该 trip 被触发时就能对这个 cooling device 下达调节指令(例如升降风扇档位、CPU/GPU 降频)。
参数解释:
thermal_zone_device *tz:要绑定的 thermal zone
int trip_index:thermal zone 的 trip point 索引(比如第 0 个高温报警点,第 1 个临界点等)
thermal_cooling_device *cdev:要绑定的 cooling device
long upper:冷却设备能用的最高 state(即最多开多大风扇,或最多降多少频)
long lower:冷却设备能用的最低 state(即最小冷却级别)
int weight:当一个 trip 绑定了多个 cooling device 时,用来衡量不同设备的重要性或分配比例
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,int trip_index,struct thermal_cooling_device *cdev,unsigned long upper, unsigned long lower,unsigned int weight)
{struct thermal_instance *dev; // 绑定关系的实例对象:把 tz + cdev + trip 这三者串起来struct thermal_instance *pos; // 遍历时的临时指针(instance)struct thermal_zone_device *pos1; // 用来在全局 tz 链表中查找 tzstruct thermal_cooling_device *pos2; // 用来在全局 cdev 链表中查找 cdevconst struct thermal_trip *trip; // 指向 tz 中的第 trip_index 个 trip pointbool upper_no_limit; // 标记 upper 是否是“无上限”int result; // 通用错误码/返回值if (trip_index >= tz->num_trips || trip_index < 0) // 边界检查:trip 下标必须合法return -EINVAL;trip = &tz->trips[trip_index]; // 取出对应的 trip 描述(只读的 trip 定义)// 在全局 thermal_tz_list 里确认 tz 确实已注册(防御性检查)list_for_each_entry(pos1, &thermal_tz_list, node) {if (pos1 == tz)break;}// 在全局 thermal_cdev_list 里确认 cdev 确实已注册(防御性检查)list_for_each_entry(pos2, &thermal_cdev_list, node) {if (pos2 == cdev)break;}if (tz != pos1 || cdev != pos2) // 任意一方没在全局列表中,视为非法return -EINVAL;/* lower 默认 0,upper 默认 max_state */lower = lower == THERMAL_NO_LIMIT ? 0 : lower; // 如果下限是 NO_LIMIT,等价于 0 档if (upper == THERMAL_NO_LIMIT) { // 上限 NO_LIMIT:等价于 cdev->max_stateupper = cdev->max_state;upper_no_limit = true; // 记下“上限原本是 NO_LIMIT”的语义} else {upper_no_limit = false;}if (lower > upper || upper > cdev->max_state) // 区间必须合法:0 <= lower <= upper <= max_statereturn -EINVAL;dev = kzalloc(sizeof(*dev), GFP_KERNEL); // 分配一个 thermal_instance(绑定对象)if (!dev)return -ENOMEM;dev->tz = tz; // 记录所属 thermal zonedev->cdev = cdev; // 记录关联的 cooling devicedev->trip = trip; // 记录绑定到的 tripdev->upper = upper; // 绑定时为该 trip 指定的 cdev 上限 statedev->upper_no_limit = upper_no_limit; // 是否来自 NO_LIMIT 的标记(便于后续逻辑)dev->lower = lower; // 绑定时为该 trip 指定的 cdev 下限 statedev->target = THERMAL_NO_TARGET; // 初始没有目标 state(由 governor 决定)dev->weight = weight; // 绑定时设置的权重(governor 会用)result = ida_alloc(&tz->ida, GFP_KERNEL); // 为该 tz 分配一个“本 tz 内唯一”的 instance idif (result < 0)goto free_mem;dev->id = result; // 保存 idsprintf(dev->name, "cdev%d", dev->id); // 生成在 sysfs 下的符号链接名:cdevNresult =sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);// 在 /sys/class/thermal/thermal_zoneX/ 下创建到 cooling_deviceY 的符号链接// 便于用户从 tz 侧看到绑定了哪些 cdevif (result)goto release_ida;// 在 tz 设备下创建只读属性:cdevN_trip_point(展示该绑定对应的 trip index)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; // 读取函数:打印 trip 信息result = device_create_file(&tz->device, &dev->attr);if (result)goto remove_symbol_link;// 在 tz 设备下创建属性:cdevN_weight(读写;用户可调整该绑定的权重)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; // 所有用户可读,owner 可写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;// 把该绑定实例挂入双方的实例链表(双向关联),注意加锁与去重mutex_lock(&tz->lock);mutex_lock(&cdev->lock);list_for_each_entry(pos, &tz->thermal_instances, tz_node)if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {// 该 tz-trip-cdev 组合已经存在,防止重复绑定result = -EEXIST;break;}if (!result) {// 尾插入 tz 的实例链表和 cdev 的实例链表(各维护一份)list_add_tail(&dev->tz_node, &tz->thermal_instances);list_add_tail(&dev->cdev_node, &cdev->thermal_instances);atomic_set(&tz->need_update, 1); // 标记 tz 需要更新(触发后续 governor 评估)}mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);if (!result) // 成功到这里则返回 0return 0;// 下面是失败时的清理回滚路径(按创建的逆序回滚)device_remove_file(&tz->device, &dev->weight_attr); // 移除权重属性文件
remove_trip_file:device_remove_file(&tz->device, &dev->attr); // 移除 trip_point 属性文件
remove_symbol_link:sysfs_remove_link(&tz->device.kobj, dev->name); // 移除 sysfs 符号链接
release_ida:ida_free(&tz->ida, dev->id); // 释放本 tz 内分配的 instance id
free_mem:kfree(dev); // 释放 instance 内存return result; // 返回错误码
}
EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device); // 导出符号,供其它模块使用
关键点:
(1)thermal_instance 的角色
它是 “关系对象”:把 tz(热区)、trip(阈值)和 cdev(冷却手段)三者绑定起来,并在该绑定上携带 上下限 state、权重、目标 state 等运行时信息。这样一个 tz 可以绑定多个不同的 cdev;同一个 cdev 也可以被多个 tz 复用。
(2)上下限处理(THERMAL_NO_LIMIT)
upper = THERMAL_NO_LIMIT → 解释为 upper = cdev->max_state,并记录 upper_no_limit = true(以保留“原本没有上限”的语义)。
lower = THERMAL_NO_LIMIT → 解释为 lower = 0。
区间校验:0 <= lower <= upper <= max_state。
(3)sysfs 可视化
在 thermal_zoneX 下创建到 cooling_deviceY 的 符号链接:cdevN,便于用户空间查看绑定关系。
暴露两个属性:
- cdevN_trip_point(只读):告诉你这个绑定挂在哪个 trip 上。
- cdevN_weight(读写):可在线调整 governor 使用该 cdev 的权重(影响调度决策)。
结构图:
3.5 thermal_zone_unbind_cooling_device
thermal_zone_unbind_cooling_device 说明:
① 作用:解除 cooling device 与 thermal zone 某个 trip point 的绑定关系。
② 调用场景:通常在 thermal zone 的 .unbind 回调中调用。
③ 返回值:0 成功,负数失败(如设备不存在)。
/*** thermal_zone_unbind_cooling_device() - unbind a cooling device from a* thermal zone.* @tz: pointer to a struct thermal_zone_device.* @trip_index: indicates which trip point the cooling devices is* associated with in this thermal zone.* @cdev: pointer to a struct thermal_cooling_device.** This interface function unbind a thermal cooling device from the certain* trip point of a thermal zone device.* This function is usually called in the thermal zone device .unbind callback.** Return: 0 on success, the proper error value otherwise.*/
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,int trip_index,struct thermal_cooling_device *cdev)
{struct thermal_instance *pos, *next; // pos/next 用于遍历 thermal_instance 链表const struct thermal_trip *trip; // trip 指向对应的 trip point// 加锁,保护 thermal zone 和 cooling device 的内部状态。// 保证解绑过程中不会有并发修改。mutex_lock(&tz->lock);mutex_lock(&cdev->lock);// 获取 tz 中对应索引的 trip point(即解绑的目标 trip)。trip = &tz->trips[trip_index];// 遍历 tz->thermal_instances 链表(保存所有绑定的 thermal_instance)。// list_for_each_entry_safe是个宏定义,用来遍历list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {// 如果 thermal_instance 同时满足:// (1) 属于当前 tz// (2) trip 相同// (3) cooling device 相同if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {list_del(&pos->tz_node); // list_del(&pos->tz_node):从 thermal zone 的链表里删掉。list_del(&pos->cdev_node); // list_del(&pos->cdev_node):从 cooling device 的链表里删掉。// 解锁,然后跳转到 unbind 执行资源清理。mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);goto unbind;}}// 如果遍历结束没找到匹配的绑定对象,则返回 -ENODEV(设备不存在)。mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);return -ENODEV;unbind:// 移除 sysfs 接口(weight 属性、trip 属性)device_remove_file(&tz->device, &pos->weight_attr); device_remove_file(&tz->device, &pos->attr);// 移除 sysfs 符号链接(之前 bind 时创建的 cdevX 软链接)sysfs_remove_link(&tz->device.kobj, pos->name);// 释放 thermal instance 的 IDida_free(&tz->ida, pos->id);// 释放 thermal_instance 内存kfree(pos);return 0;
}
EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
【对比】
bind 时会:
- 分配 thermal_instance
- 链接到 tz 和 cdev 的链表
- 在 sysfs 里创建属性和符号链接
unbind 时会:
- 从链表中删除 thermal_instance
- 删除 sysfs 节点和链接
- 回收 ID 和内存
4 设备截图
基于rk Android设备截图: