1. 为什么“thermal framework”不是个可有可无的模块,而是嵌入式与服务器场景下内核稳定性的守门人
在Linux内核功耗子系统中,“thermal framework”常被初学者误读为“只是给温度传感器加个驱动”,甚至被当作“散热风扇控制的附属逻辑”。这种理解偏差直接导致大量实际项目踩坑:某国产工控平台在高温环境下连续重启,排查数周才发现thermal zone注册顺序错误,触发了thermal governor的空指针解引用;某ARM64服务器集群在负载突增时出现CPU频率异常锁死,最终定位到thermal cooling device的state transition未做原子保护,引发竞态死锁。这些都不是边缘案例——它们恰恰暴露了一个核心事实:thermal framework是内核中少数几个同时横跨电源管理、设备驱动、调度器和中断子系统的交叉枢纽,其设计哲学不是“监测温度”,而是“主动干预系统行为以维持热安全边界”。
这个框架的通用性,体现在它不依赖具体硬件实现。你不需要为每款SoC写一套独立的温控逻辑,也不必为每个风扇型号定制驱动。相反,它定义了一套抽象契约:只要你的温度传感器能提供temperature属性,只要你的CPU或GPU支持cur_state/max_state调节接口,只要你的风扇能响应set_cur_state调用,thermal framework就能自动将它们编织成一张协同工作的热管理网络。这种解耦能力,正是它成为“通用架构”的根基。它不像cpufreq那样只管频率,也不像pm_qos那样只管延迟约束,而是把温度作为第一等公民,让整个内核围绕热状态动态调整资源分配策略。
我第一次真正理解它的分量,是在调试一款国产RISC-V SoC的车载网关板。当时系统在-40℃冷启动失败,日志里只有模糊的thermal: failed to bind trip point。翻遍文档才发现,该芯片的thermal sensor driver在probe阶段未正确初始化trip_point数组长度,而framework默认期望至少3个trip点(critical、hot、passive)。这个看似微小的驱动缺陷,直接导致整个thermal zone无法注册,进而使所有后续的cooling device绑定失败。问题根源不在framework本身,而在于开发者对它的契约理解存在断层——它不是被动接收数据的管道,而是一个需要严格履行接口协议的协调中枢。这种“契约驱动”的设计范式,正是它区别于其他子系统的关键特征。
提示:不要把thermal framework当成一个“功能模块”,而要视其为内核运行时的“热安全协议栈”。它的核心价值不在于“你知道多热”,而在于“你知道该怎么做”。
2. thermal framework的四层抽象模型:从物理传感器到策略决策的完整映射链
thermal framework的通用性并非来自代码行数的堆砌,而是源于其精巧的四层抽象模型。这四层不是简单的上下调用关系,而是逐级注入语义、剥离硬件细节、构建策略空间的过程。理解每一层的职责边界,是避免配置错乱和逻辑冲突的前提。
2.1 第一层:thermal zone —— 热域的物理建模容器
struct thermal_zone_device是整个框架的锚点。它不直接操作硬件,而是封装一个物理上具有热耦合特性的区域——比如“CPU die”、“GPU core”、“PMIC junction”或“board ambient”。关键在于,它通过ops函数指针集,将底层硬件差异完全隔离:
static const struct thermal_zone_device_ops tz_ops = { .get_temp = hisi_thermal_get_temp, .set_trips = hisi_thermal_set_trips, .notify = hisi_thermal_notify, };这里get_temp返回的是原始ADC值还是经过校准的摄氏度?set_trips是写寄存器还是触发I2C命令?notify是轮询检查还是依赖中断?对framework而言全然无关。它只关心:当温度越过某个阈值时,能否收到通知;当需要设置新阈值时,调用是否成功。这种设计让同一份framework代码,既能驱动Intel的THERM_STATUS MSR,也能驱动Rockchip的GRF_SOC_CON0寄存器,还能对接Zephyr RTOS的sensor API。
实操中最大的陷阱是thermal_zone_device_register()的参数传递。trips数组必须按温度升序排列,且type字段(THERMAL_TRIP_ACTIVE/PASSIVE/CRITICAL)决定了后续行为路径。曾遇到一个案例:某厂商将CRITICALtrip点设为120℃,但PASSIVE设为95℃,结果在98℃时系统既未触发紧急关机(因未达critical),也未启动降频(因passive trip未被governor启用)。根本原因在于thermal_zone_device_register()内部会校验trip点有效性,但错误配置不会报错,只会静默失效。因此,注册后务必用cat /sys/class/thermal/thermal_zone*/trip_point_*_temp验证顺序与数值。
2.2 第二层:trip point —— 热事件的语义化触发器
Trip point不是简单的“温度阈值”,而是带有明确行为意图的热事件标记。framework定义了四种标准类型:
| 类型 | 触发条件 | 默认行为 | 典型应用场景 |
|---|---|---|---|
THERMAL_TRIP_CRITICAL | 温度≥阈值 | 立即关机(emergency_shutdown()) | 芯片结温极限,不可逾越 |
THERMAL_TRIP_HOT | 温度≥阈值 | 向用户空间发送NETLINK消息 | 需要记录日志或告警 |
THERMAL_TRIP_PASSIVE | 温度≥阈值 | 启动被动降温(如降频、限压) | CPU/GPU性能调节主通道 |
THERMAL_TRIP_ACTIVE | 温度≥阈值 | 启动主动降温(如风扇加速) | 散热系统执行单元 |
关键洞察在于:PASSIVE和ACTIVE不是并列关系,而是协同关系。PASSIVEtrip触发governor(如step_wise)计算目标冷却状态,而ACTIVEtrip则由cooling device(如风扇)根据该状态执行物理动作。例如,当CPU温度达到PASSIVE阈值时,governor可能决定将CPU频率降至70%,这个指令通过cooling_device的set_cur_state()接口下发;当温度继续上升触发ACTIVEtrip时,风扇驱动收到指令将PWM占空比提升至80%。这种分离确保了策略层与执行层的解耦。
2.3 第三层:cooling device —— 热调控的物理执行器
struct thermal_cooling_device代表一切可调节的热耗散单元:CPU频率、GPU电压、风扇转速、甚至LCD背光亮度。它的核心接口是set_cur_state()和get_cur_state(),但真正的通用性体现在state2power()和power2state()这两个可选回调上。当系统采用power_allocatorgovernor时,framework需要将冷却状态(如CPU频率档位)映射为等效功率(mW),再据此计算各device的功率分配权重。没有这两个回调,power_allocator就退化为简单的线性比例分配。
一个典型误区是认为cooling device必须“主动上报状态”。实际上,framework采用“拉取模式”:governor在每次thermal management cycle中调用cdev->ops->get_max_state(cdev)获取当前最大可调档位,再结合cdev->ops->set_cur_state(cdev, target_state)下发指令。这意味着,即使风扇驱动尚未完成PWM初始化,只要get_max_state()返回0,framework就会跳过对该device的调控,避免无效操作。这种设计极大提升了框架的鲁棒性。
2.4 第四层:thermal governor —— 热策略的智能决策引擎
Governor是framework的“大脑”,负责根据当前温度、trip点状态和cooling device能力,计算最优冷却动作。内核目前内置五种governor,其选择逻辑远非“哪个更先进”那么简单:
step_wise:最常用,适用于大多数嵌入式场景。它采用阶梯式响应:温度每升高ΔT,就将cooling device state提升一级。优点是简单可靠,缺点是响应滞后。bang_bang:仅用于critical场景。温度低于阈值时全速运行,高于阈值时立即停机。适合风扇等二值设备。user_space:将决策权完全交给用户空间进程(如thermald)。适用于需要复杂算法(如PID控制、机器学习预测)的高端服务器。power_allocator:基于功率模型进行精细化分配。需配合state2power()回调,适合多热源协同调控(如CPU+GPU+DDR共用散热器)。fair_share:为多个cooling device分配冷却权重,避免单一device过度调节。适合多核SoC中不同cluster的差异化调控。
选择governor的本质,是权衡实时性、精度与系统开销。在车载ECU中,我坚持使用step_wise而非power_allocator,因为前者在ARM Cortex-M7上仅需200 cycles完成一次决策,而后者涉及浮点运算和查表,会显著增加中断延迟。这不是技术落后,而是对实时性边界的清醒认知。
3. thermal zone注册的隐式依赖链:为什么驱动加载顺序决定系统生死
thermal framework的“通用”特性,使其极易陷入一种隐蔽的依赖陷阱:thermal zone的注册成功,依赖于所有关联cooling device的提前注册,而cooling device的注册又依赖于其底层platform driver的就绪。这条隐式依赖链一旦断裂,系统不会报错,只会表现为“温度正常但无任何降温动作”,故障现象与根本原因之间存在巨大鸿沟。
3.1 依赖链的完整拓扑:从设备树到内核对象
以一个典型的ARM64 SoC为例,完整的依赖路径如下:
Device Tree Node (thermal-zones) ↓ 解析为 struct thermal_zone_params ↓ thermal_zone_of_sensor_register() ↓ 依赖:&thermal-sensor@12345678 (sensor driver) ↓ platform_driver_register(&hisi_thermal_driver) ↓ probe()中调用 thermal_zone_device_register() ↓ 依赖:cooling-device = <&cpu0_cooling>, <&fan0> ↓ &cpu0_cooling → cpufreq_cooling_register() ↓ 依赖:cpufreq driver已注册且policy已建立 ↓ &fan0 → pwm_fan_probe() ↓ 依赖:pwm driver已就绪且channel已request这个链条中任何一个环节延迟或失败,都会导致thermal zone注册时tz->cdevs[i]为NULL。framework对此的处理是静默跳过该cooling device,继续注册其余部分。结果就是:/sys/class/thermal/thermal_zone0/trip_point_0_temp可读,/sys/class/thermal/thermal_zone0/temp有值,但/sys/class/thermal/thermal_zone0/cdev[0-*]/cur_state全部为空——系统看起来“工作正常”,实则热保护形同虚设。
3.2 实战排错:三步定位依赖缺失
当发现thermal zone无cooling device绑定时,按以下顺序排查,效率最高:
第一步:确认zone自身注册状态
# 查看所有thermal zone ls /sys/class/thermal/ # 检查zone0的详细信息 cat /sys/class/thermal/thermal_zone0/type # 应为"cpu-thermal" cat /sys/class/thermal/thermal_zone0/temp # 应有合理数值(如45000=45℃)若temp为0或-ENODEV,则问题在sensor driver层,直接检查dmesg | grep -i thermal中sensor probe日志。
第二步:检查cooling device是否存在
# 列出所有cooling device ls /sys/class/thermal/cooling_device* # 检查cpu cooling device cat /sys/class/thermal/cooling_device0/name # 应为"cpu-thermal-cdev0" cat /sys/class/thermal/cooling_device0/cur_state # 应可读写若cooling_device*目录不存在,说明cpufreq或pwm fan驱动未加载。此时执行:
modprobe cpufreq_conservative # 确保cpufreq policy存在 ls /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq # 验证cpufreq可用第三步:追溯device tree绑定
查看/proc/device-tree/thermal-zones/cpu-thermal/trips/下的节点,确认cooling-maps属性是否正确定义了cooling-device引用。常见错误包括:
- 引用的
&cpu0_cooling节点在device tree中拼写错误(如cpu0_cooling0) cooling-device的min/max属性超出cooling device实际支持范围(如fan driver只支持0-255,但device tree设为0-1000)map中的trip索引越界(如trip点只有2个,但map引用trip=3)
注意:
thermal_zone_device_register()返回非NULL并不意味着成功。必须检查/sys/class/thermal/thermal_zoneX/cdev*目录是否存在,这才是唯一可靠的验证方式。
4. governor策略的底层执行机制:从温度采样到状态下发的毫秒级时序剖析
thermal framework的响应速度,直接决定系统在热冲击下的生存能力。很多人以为“采样频率越高越好”,却忽略了governor执行链路上的固有延迟。理解这个时序链,是优化热响应的关键。
4.1 完整执行周期:从ADC读取到PWM更新的7个关键阶段
以step_wisegovernor为例,一次完整的热调控周期包含以下阶段(单位:微秒级):
| 阶段 | 执行主体 | 典型耗时 | 关键影响因素 |
|---|---|---|---|
| 1. 温度采样 | Sensor driverget_temp() | 50~500μs | ADC转换时间、I2C总线速率、校准算法复杂度 |
| 2. Trip点比对 | Framework核心 | <1μs | trip点数量(O(n)查找) |
| 3. Governor决策 | step_wise_throttle() | 2~10μs | 当前state、target state计算 |
| 4. Cooling device状态查询 | cdev->ops->get_cur_state() | 1~50μs | PWM寄存器读取、cpufreq policy查询 |
| 5. 状态合法性校验 | Framework校验逻辑 | <1μs | target_state ≤ max_state检查 |
| 6. 状态下发 | cdev->ops->set_cur_state() | 10~200μs | I2C写入、MSR写入、寄存器批量更新 |
| 7. 状态生效延迟 | 硬件物理响应 | 10ms~500ms | 风扇机械惯性、CPU PLL锁定时间、电压环路响应 |
可见,软件层面的决策耗时(阶段1-6)通常在100μs以内,而真正的瓶颈在阶段7——硬件物理响应。这意味着,将采样间隔从1000ms缩短到100ms,对风扇转速调节几乎无改善,反而因频繁I2C通信增加系统负载。但在CPU降频场景下,阶段6的MSR写入耗时极短(<1μs),此时缩短采样间隔才能真正提升响应速度。
4.2 采样间隔的科学设定:平衡精度与开销的黄金法则
内核通过polling_delay参数控制采样间隔,其设定必须遵循以下原则:
对于被动调控设备(CPU/GPU频率):
polling_delay应≤thermal_latency(设备热响应时间)。例如,ARM Cortex-A76的thermal_latency约为200ms,故polling_delay设为100ms是合理的。过长会导致温度飙升后才开始降频。对于主动调控设备(风扇):
polling_delay应≥mechanical_response_time(机械响应时间)。典型轴流风扇的mechanical_response_time为300~500ms,若设为50ms,会导致PWM信号频繁抖动,既无实际降温效果,又增加电机磨损。
实测数据佐证:在某RK3399平台上,将polling_delay从1000ms改为100ms后,CPU温度波动幅度从±8℃收窄至±3℃;但进一步缩短至20ms,温度波动未改善,而I2C总线占用率从5%飙升至35%,导致触摸屏报告延迟增加。
4.3 Governor切换的原子性保障:为何不能在运行时随意更换
thermal_governor结构体中的throttle函数指针,在thermal_zone_device_update()中被直接调用。这意味着governor切换不是“优雅过渡”,而是指针替换。若在governor执行中途切换,可能导致:
- 新governor的私有数据结构未初始化,访问时触发oops
- 旧governor的pending state未清理,造成状态不一致
内核为此设计了严格的切换协议:
- 必须在
thermal_zone_device_unregister()后,才能thermal_zone_device_register()新zone - 用户空间通过
echo "step_wise" > /sys/class/thermal/thermal_zone0/policy切换时,framework会先调用旧governor的unbind()回调(如有),再调用新governor的bind()回调 bind()回调中必须完成所有私有数据初始化,否则后续throttle()调用必然崩溃
因此,生产环境中应避免运行时切换governor。最佳实践是在设备树中静态指定governor = "step_wise",或在driver probe中通过tz->governor = &thermal_gov_step_wise硬编码绑定。
5. 嵌入式场景下的致命陷阱:内存布局、中断延迟与thermal framework的隐式冲突
在资源受限的嵌入式系统中,thermal framework的通用设计会暴露出一些在x86服务器上完全不存在的致命问题。这些问题往往与内核内存管理、中断子系统深度耦合,需要从硬件底层重新审视。
5.1 内存分配失败:thermal zone注册时的kmalloc陷阱
thermal_zone_device_register()内部会为trip_points、cdevs等数组分配内存。在RAM仅256MB的ARM Cortex-A53平台上,若同时注册10个thermal zone,每个zone配置8个trip点和4个cooling device,内存消耗可达:
trip_points: 8 × sizeof(struct thermal_trip) ≈ 8 × 32B = 256Bcdevs: 4 × sizeof(struct thermal_cooling_device *) ≈ 4 × 8B = 32Btzp(params): 1 × sizeof(struct thermal_zone_params) ≈ 64B
单zone约352B,10个zone仅3.5KB——看似微不足道。但问题在于,这些内存由kmalloc()分配,而kmalloc()在低内存压力下可能返回NULL。framework对此的处理是:
tz = kzalloc(sizeof(*tz), GFP_KERNEL); if (!tz) return ERR_PTR(-ENOMEM); // ... 后续分配trip_points等,失败时goto free_tz然而,GFP_KERNEL在中断上下文或内存极度紧张时可能sleep,导致注册过程被阻塞。更危险的是,某些SoC的thermal sensor driver在probe()中调用thermal_zone_device_register(),而probe()本身就在platform bus的中断上下文中执行——这直接违反了GFP_KERNEL的使用前提。
解决方案是强制使用GFP_ATOMIC:
tz = kzalloc(sizeof(*tz), GFP_ATOMIC); // 在probe中必须用ATOMIC if (!tz) return ERR_PTR(-ENOMEM);但这又带来新问题:GFP_ATOMIC池极小,大块内存分配易失败。因此,嵌入式驱动必须预估最大内存需求,并在device tree中限制trip点数量(如#trip-points-cells = <2>),避免动态分配过大数组。
5.2 中断延迟:thermal notify的实时性危机
thermal_zone_device的notify回调,通常在温度越过trip点时由sensor driver的中断服务程序(ISR)调用。ISR的执行时间必须控制在微秒级,否则会阻塞其他高优先级中断(如CAN总线、ADC采样)。
问题在于,notify()默认实现是:
void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { // ... 大量链表遍历、mutex_lock等操作 }其中mutex_lock(&tz->lock)在ISR中是绝对禁止的!内核为此提供了thermal_zone_device_update_no_lock(),但要求driver必须保证notify调用发生在tasklet或workqueue上下文。
实操中,我们为Hisi thermal driver添加了专用workqueue:
static struct work_struct hisi_thermal_work; static void hisi_thermal_notify_work(struct work_struct *work) { thermal_zone_device_update_no_lock(hisi_tz, THERMAL_TRIP_CHANGED); } static irqreturn_t hisi_thermal_irq(int irq, void *data) { schedule_work(&hisi_thermal_work); // ISR中只调度work return IRQ_HANDLED; }这样,ISR执行时间稳定在1μs内,而实际的thermal update在softirq上下文中完成,既满足实时性,又保证了framework的完整性。
5.3 设备树覆盖:如何安全地为国产SoC定制thermal配置
国产SoC厂商常提供不完整的device tree thermal描述,导致framework无法正确识别trip点。此时不能直接修改vendor提供的dtsi文件(易被升级覆盖),而应使用overlay机制:
// thermal-overlay.dts /dts-v1/; /plugin/; / { fragment@0 { target = <&cpu_thermal>; __overlay__ { trips { cpu_crit: cpu-crit { temperature = <105000>; // 105℃ hysteresis = <2000>; // 2℃迟滞 type = "critical"; }; cpu_passive: cpu-passive { temperature = <85000>; // 85℃ hysteresis = <1000>; type = "passive"; }; }; }; }; };编译为thermal-overlay.dtbo后,在bootargs中添加:
dtoverlay=thermal-overlay此方法的优势在于:overlay独立于主dts,可随固件单独升级;所有trip点参数均可在用户空间通过/sys/firmware/devicetree/base/...验证,避免硬编码风险。
6. 生产环境调试的终极武器:sysfs接口的深度挖掘与自定义监控脚本
framework暴露的sysfs接口远不止temp和cur_state,其深层节点是定位热管理故障的黄金矿藏。掌握这些接口的读取逻辑和含义,能让调试效率提升十倍。
6.1 核心sysfs节点的实战解读
| 路径 | 读取方式 | 含义 | 调试价值 |
|---|---|---|---|
/sys/class/thermal/thermal_zone0/mode | cat | enabled/disabled | 确认thermal framework是否激活,disabled表示整个子系统被禁用 |
/sys/class/thermal/thermal_zone0/policy | cat/echo | 当前governor名称 | 验证governor是否按预期加载 |
/sys/class/thermal/thermal_zone0/passive | cat | 1表示passive trip已触发 | 判断是否进入降频状态,而非仅看温度 |
/sys/class/thermal/cooling_device0/cur_state | cat | 当前冷却档位(0~max) | 直接观察调控动作是否执行 |
/sys/class/thermal/cooling_device0/max_state | cat | 最大可调档位 | 验证cooling device能力是否被正确识别 |
/sys/class/thermal/thermal_zone0/emul_temp | echo 90000 > | 强制模拟温度 | 无需加热硬件即可测试trip点响应 |
特别注意emul_temp:它绕过真实sensor,直接向framework注入温度值。这是验证整个调控链路(sensor→trip→governor→cdev)是否通畅的最快方法。例如:
# 模拟温度升至90℃,触发passive trip echo 90000 > /sys/class/thermal/thermal_zone0/emul_temp # 立即检查cooling device状态是否变化 watch -n 0.1 'cat /sys/class/thermal/cooling_device0/cur_state'若cur_state未变化,则问题一定在governor或cooling device绑定环节。
6.2 自动化监控脚本:捕获瞬态热事件的利器
瞬态温度尖峰(如CPU burst)常在日志中一闪而过。为此,我编写了一个轻量级监控脚本,以10ms粒度持续采样:
#!/bin/bash # thermal-monitor.sh ZONE="/sys/class/thermal/thermal_zone0" LOG="/var/log/thermal-burst.log" THRESHOLD=80000 # 80℃ echo "$(date): Thermal monitor started" >> $LOG while true; do TEMP=$(cat $ZONE/temp 2>/dev/null) if [ -n "$TEMP" ] && [ "$TEMP" -gt "$THRESHOLD" ]; then # 记录爆发时刻的完整状态 echo "$(date +%s.%N): BURST DETECTED! Temp=$TEMP" >> $LOG echo " Policy: $(cat $ZONE/policy)" >> $LOG echo " Passive: $(cat $ZONE/passive)" >> $LOG echo " Cdev0 state: $(cat $ZONE/cdev0/cur_state)" >> $LOG echo " Cdev0 max: $(cat $ZONE/cdev0/max_state)" >> $LOG # 触发一次紧急dump echo 1 > /proc/sys/kernel/sysrq echo w > /proc/sysrq-trigger fi usleep 10000 # 10ms间隔 done该脚本的核心价值在于:它不依赖systemd或复杂框架,以最小开销运行,且能精确捕获毫秒级温度尖峰。在某次车载导航系统死机复现中,正是靠它抓取到CPU温度在200ms内从65℃飙升至92℃的完整过程,从而定位到GPU驱动未正确释放memory bandwidth导致的热堆积。
经验之谈:不要依赖
dmesg查看thermal日志。framework的pr_debug()输出默认关闭,且printk在高负载下会丢帧。sysfs是唯一实时、可靠的状态源。
7. 从源码看设计哲学:thermal.h头文件中被忽视的12个关键注释
内核头文件include/linux/thermal.h中的注释,是理解framework设计意图的第一手资料。这些注释不是文档补充,而是开发者在实现时写下的血泪教训。以下是12条最具指导价值的注释解析:
/* Do not use this in interrupt context */
出现在thermal_zone_device_update()声明前。直指核心:thermal update必须在进程上下文执行。这是前述中断延迟问题的法理依据。/* This is a read-only field for user space */
注释在struct thermal_zone_device的temperature字段旁。意味着用户空间只能读取,任何写入尝试(如echo 50000 > temp)都会被忽略——这是防止误操作的安全栅栏。/* Must be called before any thermal_zone_device_register() */
针对thermal_add_hwmon_sysfs()。揭示了hwmon接口的初始化顺序依赖,解释了为何某些平台/sys/class/hwmon/下无thermal sensor。/* The cooling device must be registered before thermal zone */
在thermal_zone_device_register()参数说明中。这是前述依赖链的官方确认,也是调试时首要检查项。/* Use thermal_zone_get_temp() instead of direct access */
注释在tz->temperature字段旁。强调封装性:直接读tz->temperature可能得到陈旧值,必须调用thermal_zone_get_temp()触发fresh read。/* This function may sleep, do not call from atomic context */
出现在thermal_cooling_device_register()中。再次警示内存分配风险,与前述GFP_KERNEL陷阱呼应。/* The trip point array must be sorted in ascending order */
在struct thermal_zone_params定义旁。解释了为何错误排序的trip点会导致静默失效——framework的二分查找逻辑依赖此前提。/* This is the default governor, do not change unless you know what you are doing */
注释在thermal_gov_step_wise定义处。step_wise不是“低端选择”,而是经过十年验证的工业级默认方案。/* The state value is device-specific, see cooling device documentation */
在struct thermal_cooling_device_ops的set_cur_state()参数旁。提醒开发者:state=3对CPU可能代表70%频率,对风扇可能代表30%PWM,绝不可跨device假设含义。/* This callback is optional, but required for power_allocator governor */
针对state2power()。解释了为何power_allocator在缺少此回调时会退化——它不是bug,而是设计契约。/* The hysteresis value prevents chattering around trip point */
在struct thermal_trip定义中。hysteresis不是精度补偿,而是抗抖动设计,其值必须大于温度传感器的噪声带宽。/* This is a kernel internal API, do not use in drivers */
出现在__thermal_zone_device_update()前。划清界限:驱动必须使用公开API,内部函数变更不受ABI保证。
这些注释共同勾勒出framework的设计底线:它不是一个可以随意hack的模块,而是一套有严格契约、明确边界、不容妥协的热安全协议。尊重这些注释,就是尊重十年来无数嵌入式系统在高温高压下的稳定运行历史。
我在实际项目中最深刻的体会是:thermal framework的“通用”,从来不是指“开箱即用”,而是指“契约清晰、边界明确、错误可溯”。当你在device tree中写下cooling-device = <&cpu0_cooling>时,你不是在配置一个功能,而是在签署一份热安全责任书——这份责任书的条款,就藏在thermal.h那十二行朴素的注释里。