前言
前面介绍过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 的全局唯一 ID
    char *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_device
		ret = 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 device
	for (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 链表中查找 tz
	struct thermal_cooling_device *pos2;   // 用来在全局 cdev 链表中查找 cdev
	const struct thermal_trip *trip;       // 指向 tz 中的第 trip_index 个 trip point
	bool 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_state
		upper = 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_state
		return -EINVAL;
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);          // 分配一个 thermal_instance(绑定对象)
	if (!dev)
		return -ENOMEM;
	dev->tz = tz;                                     // 记录所属 thermal zone
	dev->cdev = cdev;                                 // 记录关联的 cooling device
	dev->trip = trip;                                 // 记录绑定到的 trip
	dev->upper = upper;                               // 绑定时为该 trip 指定的 cdev 上限 state
	dev->upper_no_limit = upper_no_limit;             // 是否来自 NO_LIMIT 的标记(便于后续逻辑)
	dev->lower = lower;                               // 绑定时为该 trip 指定的 cdev 下限 state
	dev->target = THERMAL_NO_TARGET;                  // 初始没有目标 state(由 governor 决定)
	dev->weight = weight;                             // 绑定时设置的权重(governor 会用)
	result = ida_alloc(&tz->ida, GFP_KERNEL);         // 为该 tz 分配一个“本 tz 内唯一”的 instance id
	if (result < 0)
		goto free_mem;
	dev->id = result;                                 // 保存 id
	sprintf(dev->name, "cdev%d", dev->id);            // 生成在 sysfs 下的符号链接名:cdevN
	result =
	    sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
	// 在 /sys/class/thermal/thermal_zoneX/ 下创建到 cooling_deviceY 的符号链接
	// 便于用户从 tz 侧看到绑定了哪些 cdev
	if (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)                                   // 成功到这里则返回 0
		return 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 的 ID
	ida_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设备截图:

 
                   
                   
                   
                   
                             
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   3299
					3299
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            