当前位置: 首页 > news >正文

Android thermal (7)_thermal core

前言

前文分析过thermal zone、cooling devic以及thermal governor,这几个部分都是独立实现的,彼此之间需要一个串接者,将各部逻辑串联到一起。所以,thermal core来了。

thermal core 是 Linux 内核热管理的核心模块,属于 thermal subsystem 的中心调度层。连接 温度传感器 (thermal zone) 和 散热装置 (cooling device) 的“大脑”,并通过 governor 制定调节策略。具体作用如下:

  • 抽象出 thermal zone(热源/温度监控点)。
  • 抽象出 cooling device(散热设备/降温手段)。
  • 提供 governor 策略(调度策略,例如 step_wise, power_allocator)。
  • 提供 用户空间接口(sysfs,UEvent,netlink)。
  • 负责 协调 温度检测与散热执行,让系统不会过热或功耗过高。

thermal core 的组成:

  • Thermal Zone (热区/热源):温度传感器或逻辑区域(CPU/GPU/Battery)。
  • Cooling Device (散热设备):可以降低热量的硬件/软件机制。
  • Governor (调度器/策略):决定如何在 cooling device 上施加控制。
  • Core 管理逻辑:注册/注销前面几个对象,定期采样温度或响应中断,调用 governor 来决定 cooling device 的状态等。
  • 用户空间接口:生成sysfs 节点:/sys/class/thermal/。kobject_uevent_env() 向用户空间发送 thermal 事件。

源码说明:

  • (1)thermal_core:三大模块注册、管理等函数实现
  • (2)thermal_of:以thermal_of开头的函数,一般作用是初始化 thermal zone 和 cooling device的基本配置
  • (3)thermal_netlink:用户空间的接口
  • (4)thermal_sysfs:/sys/class/thermal/ 节点创建、维护函数
  • (5)thermal_helpers:thermal zone 和 cooling device 对状态、温度等值读/写的方法
  • (6)thermal_trip:thermal zone trip相关的函数实现

1 thermal core的初始化

1.1 thermal_init

thermal_init() 是 thermal 子系统的启动入口(以 postcore_initcall 在内核启动流程中被调用),依次完成:netlink 初始化 → 注册 governor → 分配并注册 class → 注册电源管理通知;在任一步骤失败时按逆序清理已分配的资源并返回错误码。

函数定义 __init:只在内核启动阶段使用,函数和常量可在启动完成后被丢弃(节约内存)。

static int __init thermal_init(void)
{int result; // 用于保存被调用函数的返回值,为初始化各阶段的判断依据。// 通常用于创建一个 netlink 通道 / socket,让内核 thermal subsystem 能与用户空间(daemon、test tools)通信result = thermal_netlink_init();if (result)goto error;// 注册内核中可用的 thermal governor,后面详细看。result = thermal_register_governors();if (result)goto unregister_netlink; // 错误处理,清理上一步创建成功的结果,回滚。// 为 struct class 分配内存并置零(kzalloc)。GFP_KERNEL 表示可睡眠分配(在初始化时通常允许)。thermal_class = kzalloc(sizeof(*thermal_class), GFP_KERNEL);if (!thermal_class) {result = -ENOMEM;goto unregister_governors; // 错误处理,清理上一步创建成功的结果,回滚。}thermal_class->name = "thermal"; // 把 class 的 name 指向字面量 "thermal"thermal_class->dev_release = thermal_release; // 设置 dev_release 回调为 thermal_release// class_register() 将 class 注册到 sysfs(/sys/class/thermal)// 户空间可见并可创建基于该 class 的设备节点(如 thermal_zone*、cooling_device*result = class_register(thermal_class);if (result) {kfree(thermal_class); // 释放之前分配的 thermal_class 内存thermal_class = NULL;goto unregister_governors; // 错误处理,清理上一步创建成功的结果,回滚。}// 向电源管理子系统注册一个 notifier(thermal_pm_nb)// 用于在系统 suspend/resume(或其他 PM 事件)时得到回调,thermal core 可借此做特殊处理result = register_pm_notifier(&thermal_pm_nb);if (result)// 如果注册 PM notifier 失败:不是致命错误。可以继续。pr_warn("Thermal: Can not register suspend notifier, return %d\n",result);return 0;unregister_governors:thermal_unregister_governors(); // 卸载之前注册的 governors
unregister_netlink:thermal_netlink_exit(); // 关闭并释放 netlink 资源
error:// 销毁(destroy)两个 mutex(thermal_list_lock 与 thermal_governor_lock)mutex_destroy(&thermal_list_lock);mutex_destroy(&thermal_governor_lock);return result; //返回错误码
}
// postcore_initcall 在内核启动流程中被调用,向内核添加thermal core
postcore_initcall(thermal_init); 

【关键步骤】
步骤一
调用 thermal_netlink_init():通常用于创建一个 netlink 通道 / socket,让内核 thermal subsystem 能与用户空间(daemon、test tools)通信。这一步让 thermal 文件系统有意义了,/sys/class/thermal 可以被用户空间访问。

步骤二
thermal_register_governors 向内核注册thermal governor管理策略,策略决定温控的执行效率。

步骤三
register_pm_notifier():向电源管理子系统注册一个 notifier(thermal_pm_nb),用于在系统 suspend/resume(或其他 PM 事件)时得到回调,thermal core 可借此做特殊处理(例如在 suspend 时调整策略)。温升/功耗基本是不分家的,因此在控温的情况,是否需要严格控制功耗,需要电源管理系统支持。

1.2 thermal_netlink

genl_family 声明 + thermal_netlink_init/exit 在内核启动时构造并注册了一个 Generic Netlink family(用于 thermal ←→ userspace 的控制与事件通道)。

thermal_netlink_init() 调用 genl_register_family() 注册该 family,thermal_netlink_exit() 注销。此 family 的名字/版本/属性/多播组/命令/属性解析规则都在 thermal_gnl_family 里声明。

1.2.1 thermal_gnl_family

static struct genl_family thermal_gnl_family __ro_after_init = {.hdrsize	= 0,.name		= THERMAL_GENL_FAMILY_NAME,.version	= THERMAL_GENL_VERSION,.maxattr	= THERMAL_GENL_ATTR_MAX,.policy		= thermal_genl_policy,.small_ops	= thermal_genl_ops,.n_small_ops	= ARRAY_SIZE(thermal_genl_ops),.resv_start_op	= THERMAL_GENL_CMD_CDEV_GET + 1,.mcgrps		= thermal_genl_mcgrps,.n_mcgrps	= ARRAY_SIZE(thermal_genl_mcgrps),
};
  • __ro_after_init
    该属性把数据放到 .data…ro_after_init 段,内核在初始化结束后会把这段内存变为只读。适合“只在 init 写一次、之后只读”的静态数据(比如这个 family 描述)。
  • .hdrsize = 0
    指定 generic-netlink 的“私有 header”大小;0 表示不添加额外的家族特定 header(handlers 直接使用 standard genl header + attrs)。
  • .name = THERMAL_GENL_FAMILY_NAME / .version = THERMAL_GENL_VERSION
    family 的名字与版本,由 UAPI 宏定义(userspace 通过相同名字/版本来打开该 family)。不同内核树/版本里宏名或字符串可能略有差别(比如 “thermal”、“thermal_event” 等),要以目标内核的 include/uapi/linux/thermal.h 为准。
  • .maxattr = THERMAL_GENL_ATTR_MAX / .policy = thermal_genl_policy
    ① maxattr:声明属性的最大编号(用于 attribute 索引与边界检查)。
    ② policy:是一个 struct nla_policy[],用来对收到 netlink 消息的 attribute 做类型与长度校验(例如 NLA_U32, NLA_NESTED 等),避免 userspace 传入非法 TLV。thermal 的 policy 会定义像 TZ、TZ_ID、TZ_TEMP、CDEV 等属性的类型/嵌套结构。
  • .small_ops / .n_small_ops
    这里使用了 generic-netlink 的 small_ops 接口(相对于传统 .ops/.n_ops)。small_ops 是后来加入的一个轻量化注册方式,适合那些处理逻辑简单、消息体小的命令,可以减少内核在处理时的开销。thermal 使用 thermal_genl_ops 把若干命令映射到处理函数。
  • .resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1
    这是个兼容性/策略项:generic-netlink core 对于小于 resv_start_op 的操作允许不使用 policy(即老命令/保留命令不会由 core 自动解析/验证 attributes),而从 resv_start_op 开始的命令如果没有 policy 将被拒绝。这个机制帮助保持向后兼容并区分“老命令”和“新命令”的 attribute 验证策略。简而言之:作者把 THRMAL_GENL_CMD_CDEV_GET 及之前的命令视作“保留/兼容”区。
  • .mcgrps / .n_mcgrps
    定义该 family 的 multicast groups(热事件通常通过 multicast 发送给订阅的 userspace)。thermal 常见的组名有 event / sampling 等,userspace 可以订阅这些组以接收 trip、critical、device-fault、采样等事件通知。

1.2.2 thermal_genl_xxx

(1)struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1]
nla_policy 数组,列出每个 attribute 的类型(例如 THERMAL_GENL_ATTR_TZ_ID → NLA_U32,THERMAL_GENL_ATTR_TZ → NLA_NESTED),用于由 netlink core/handler 做 attribute 验证并安全抽取数据.

static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {/* Thermal zone */[THERMAL_GENL_ATTR_TZ]			= { .type = NLA_NESTED },[THERMAL_GENL_ATTR_TZ_ID]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_TEMP]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_TRIP]		= { .type = NLA_NESTED },[THERMAL_GENL_ATTR_TZ_TRIP_ID]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_TRIP_HYST]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_MODE]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_TZ_NAME]		= { .type = NLA_STRING,.len = THERMAL_NAME_LENGTH },/* Governor(s) */[THERMAL_GENL_ATTR_TZ_GOV]		= { .type = NLA_NESTED },[THERMAL_GENL_ATTR_TZ_GOV_NAME]		= { .type = NLA_STRING,.len = THERMAL_NAME_LENGTH },/* Cooling devices */[THERMAL_GENL_ATTR_CDEV]		= { .type = NLA_NESTED },[THERMAL_GENL_ATTR_CDEV_ID]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_CDEV_CUR_STATE]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_CDEV_MAX_STATE]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_CDEV_NAME]		= { .type = NLA_STRING,.len = THERMAL_NAME_LENGTH },/* CPU capabilities */[THERMAL_GENL_ATTR_CPU_CAPABILITY]		= { .type = NLA_NESTED },[THERMAL_GENL_ATTR_CPU_CAPABILITY_ID]		= { .type = NLA_U32 },[THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE]	= { .type = NLA_U32 },[THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY]	= { .type = NLA_U32 },
};

(2)struct genl_small_ops thermal_genl_ops[]
命令到处理函数的映射表(例如 THERMAL_GENL_CMD_CDEV_GET → thermal_genl_cmd_cdev_get()),在 small_ops 模式下这些处理函数会被 generic-netlink core 调用来响应请求。

static const struct genl_small_ops thermal_genl_ops[] = {{.cmd = THERMAL_GENL_CMD_TZ_GET_ID,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.dumpit = thermal_genl_cmd_dumpit,},{.cmd = THERMAL_GENL_CMD_TZ_GET_TRIP,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.doit = thermal_genl_cmd_doit,},{.cmd = THERMAL_GENL_CMD_TZ_GET_TEMP,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.doit = thermal_genl_cmd_doit,},{.cmd = THERMAL_GENL_CMD_TZ_GET_GOV,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.doit = thermal_genl_cmd_doit,},{.cmd = THERMAL_GENL_CMD_CDEV_GET,.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,.dumpit = thermal_genl_cmd_dumpit,},
};

(3)struct genl_multicast_group thermal_genl_mcgrps[]
列出可用的 multicast 组(事件/采样分类),用于 genl_multicast_group 注册和 genlmsg_multicast() 发送事件。

static const struct genl_multicast_group thermal_genl_mcgrps[] = {{ .name = THERMAL_GENL_SAMPLING_GROUP_NAME, },{ .name = THERMAL_GENL_EVENT_GROUP_NAME,  },
};

1.2.3 init() & exit()

int __init thermal_netlink_init(void)
{return genl_register_family(&thermal_gnl_family);
}void __init thermal_netlink_exit(void)
{genl_unregister_family(&thermal_gnl_family);
}
  • genl_register_family
    把 thermal_gnl_family 注册到 generic-netlink core —— 分配 family id、注册 ops 和 multicast group 等。返回 0 表示成功,否则返回负 errno。thermal 的初始化流程(thermal_init)会把该返回值用于失败回退处理。
  • genl_unregister_family
    注销该 family,清理 core 的数据结构,多播组也随之失效。用于模块卸载或 init 失败后的回滚。

1.3 thermal_pm_notify

notifier_block:Linux 内核的通知链机制(notifier chain),允许多个子系统监听某些事件。这里 thermal core 定义了一个 电源管理(PM)通知回调块,当系统进入 suspend/hibernate 或恢复时,会调用 thermal_pm_notify()。

参数:

  • nb:对应的 notifier_block(这里就是 thermal_pm_nb)。
  • mode:通知类型,表示当前 PM 事件,例如 PM_SUSPEND_PREPARE、PM_POST_SUSPEND 等。
  • _unused:没用到的额外参数。
static struct notifier_block thermal_pm_nb = {.notifier_call = thermal_pm_notify,
};static int thermal_pm_notify(struct notifier_block *nb,unsigned long mode, void *_unused)
{struct thermal_zone_device *tz;switch (mode) {// 系统进入休眠/挂起前case PM_HIBERNATION_PREPARE:case PM_RESTORE_PREPARE:case PM_SUSPEND_PREPARE:mutex_lock(&thermal_list_lock);// 遍历 thermal core 管理的所有 thermal zonelist_for_each_entry(tz, &thermal_tz_list, node) {// 进入具体的 thermal zone,需要加 tz->lock 保护 zone 内部状态mutex_lock(&tz->lock); // 设置挂起状态。标记 thermal zone 已挂起,意味着// (1) 不会再触发温度更新。// (2) 不会调用 cooling device 进行调节。// (3) 防止 thermal 干扰系统挂起流程。tz->suspended = true;mutex_unlock(&tz->lock);}// 遍历结束,解开全局 thermal list 锁mutex_unlock(&thermal_list_lock);break;// 系统恢复后,系统从休眠/挂起恢复。case PM_POST_HIBERNATION:case PM_POST_RESTORE:case PM_POST_SUSPEND:mutex_lock(&thermal_list_lock);// 逐个恢复 thermal zone。list_for_each_entry(tz, &thermal_tz_list, node) {mutex_lock(&tz->lock);// 解除挂起状态tz->suspended = false;// 重新初始化 thermal zonethermal_zone_device_init(tz);// 主动触发一次温度更新__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);mutex_unlock(&tz->lock);}// 解全局锁mutex_unlock(&thermal_list_lock);break;default:break;}return 0;
}

【关键点】
thermal_zone_device_init :重新初始化 zone。可能重新配置 trip 点。重新启用 sensor 回调。为恢复后的正常 thermal 调度做准备。

__thermal_zone_device_update:立即触发 thermal 更新流程,读取最新温度,判断是否越过 trip 点,通知 cooling device 执行相应操作(如 CPU 降频)。参数 THERMAL_EVENT_UNSPECIFIED 表示这是一次普通更新,不对应特定事件(如高温报警)。

【注】
thermal core 初始化过程中,没有看到 thermal zone 和 cooling device 的注册。这是因为在最新的kernel中,这两部分的注册调用都在对应的硬件初始化时进行了。如thermal sensor初始化时,就调用过 thermal zone 的注册逻辑。
因此,对于thermal core 我们需要关注的是如何实现这些注册逻辑的,如何管理这些温控策略的,如何有效触发温控设备运行或者上报的。

2 thermal设备注册

2.1 thermal zone 注册

thermal zone的注册流程如下:

rockchip_thermal_register_sensor // 从[thermal sensor]那章节切入
↓
devm_thermal_of_zone_register // [thermal zone] 可以看
↓
thermal_of_zone_register // [thermal zone] 可以看
↓
thermal_zone_device_register_with_trips

2.1.1 thermal_zone_device_register_with_trips

thermal_zone_device_register_with_trips函数是 Linux kernel thermal 子系统里最核心的注册函数之一,负责注册一个 thermal zone(温度传感器管理单元)函数。函数定义如下:

函数参数:

  • type:zone 名称字符串
  • trips:热触发点数组(trip points,例如温度阈值)
  • num_trips:trip 数量
  • mask:bitmask,标记哪些 trip 支持写操作
  • devdata:驱动私有数据指针
  • ops:操作函数集(回调,比如 .get_temp, .set_trips 等)
  • tzp:platform 参数(如 governor 名称、是否禁用 hwmon)
  • passive_delay / polling_delay:轮询延迟(被动冷却 / 温度检测)
/*** thermal_zone_device_register_with_trips() - register a new thermal zone device* @type:	the thermal zone device type* @trips:	a pointer to an array of thermal trips* @num_trips:	the number of trip points the thermal zone support* @mask:	a bit string indicating the writeablility of trip points* @devdata:	private device data* @ops:	standard thermal zone device callbacks* @tzp:	thermal zone platform parameters* @passive_delay: number of milliseconds to wait between polls when*		   performing passive cooling* @polling_delay: number of milliseconds to wait between polls when checking*		   whether trip points have been crossed (0 for interrupt*		   driven systems)** This interface function adds a new thermal zone device (sensor) to* /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the* thermal cooling devices registered at the same time.* thermal_zone_device_unregister() must be called when the device is no* longer needed. The passive cooling depends on the .get_trend() return value.** Return: a pointer to the created struct thermal_zone_device or an* in case of error, an ERR_PTR. Caller must check return value with* IS_ERR*() helpers.*/
struct thermal_zone_device *
thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *trips, int num_trips, int mask,void *devdata, struct thermal_zone_device_ops *ops,const struct thermal_zone_params *tzp, int passive_delay,int polling_delay)
{struct thermal_zone_device *tz; //thermal zone对象指针int id;int result;int count;struct thermal_governor *governor; // governor对象指针// 判断type是否非空if (!type || strlen(type) == 0) {pr_err("No thermal zone type defined\n");return ERR_PTR(-EINVAL);}// 判断type字节长度if (strlen(type) >= THERMAL_NAME_LENGTH) {pr_err("Thermal zone name (%s) too long, should be under %d chars\n",type, THERMAL_NAME_LENGTH);return ERR_PTR(-EINVAL);}/** Max trip count can't exceed 31 as the "mask >> num_trips" condition.* For example, shifting by 32 will result in compiler warning:* warning: right shift count >= width of type [-Wshift-count- overflow]** Also "mask >> num_trips" will always be true with 32 bit shift.* E.g. mask = 0x80000000 for trip id 31 to be RW. Then* mask >> 32 = 0x80000000* This will result in failure for the below condition.** Check will be true when the bit 31 of the mask is set.* 32 bit shift will cause overflow of 4 byte integer.*/// 校验 trip 参数// 限制 trip 数量(必须在 0 ~ 31 内)。避免位移溢出 (mask >> num_trips)。if (num_trips > (BITS_PER_TYPE(int) - 1) || num_trips < 0 || mask >> num_trips) {pr_err("Incorrect number of thermal trips\n");return ERR_PTR(-EINVAL);}// 回调对象校验if (!ops) {pr_err("Thermal zone device ops not defined\n");return ERR_PTR(-EINVAL);}// 校验tripsif (num_trips > 0 && !trips)return ERR_PTR(-EINVAL);// 校验全局 thermal_class 是否初始化// thermal 子系统还没初始化时,不能注册 zoneif (!thermal_class)return ERR_PTR(-ENODEV);// 分配 thermal_zone_device 存储单元tz = kzalloc(sizeof(*tz), GFP_KERNEL);if (!tz)return ERR_PTR(-ENOMEM);// 判断传入参数 thermal_zone_params *tzp 有效性if (tzp) {// 拷贝参数到定义的thermal zonetz->tzp = kmemdup(tzp, sizeof(*tzp), GFP_KERNEL);if (!tz->tzp) {result = -ENOMEM;goto free_tz;}}// 初始化链表、ID 管理器、互斥锁和移除完成量INIT_LIST_HEAD(&tz->thermal_instances); // struct list_head thermal_instancesINIT_LIST_HEAD(&tz->node); // struct list_head nodeida_init(&tz->ida); // struct ida idamutex_init(&tz->lock); // struct mutex lockinit_completion(&tz->removal); // struct completion removal// 分配一个全局 thermal zone id,例如 thermal_zone0id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);if (id < 0) {result = id;goto free_tzp;}//thermal zone:id 赋值tz->id = id;//thermal zone:type保存,即名称 strscpy(tz->type, type, sizeof(tz->type));// ops->critical 没实现,使用默认实现 thermal_zone_device_critical 函数。if (!ops->critical)ops->critical = thermal_zone_device_critical;tz->ops = ops; // ops 赋值给本地对象tz->device.class = thermal_class; // class 赋值给本地对象tz->devdata = devdata; // devdata 赋值给本地对象tz->trips = trips; // trips 赋值给本地对象tz->num_trips = num_trips; // num_trips 赋值给本地对象// 把 delay 转换为 jiffies 存储thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay); // 高温轮询间隔thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay); // 常规轮询间隔/* sys I/F *//* Add nodes that are always present via .groups */// 创建 sysfs 属性组,即 /sys/class/thermal/thermal_zone%d/下面的节点result = thermal_zone_create_device_groups(tz, mask);if (result)goto remove_id;/* A new thermal zone needs to be updated anyway. */atomic_set(&tz->need_update, 1);// 设置设备名字 thermal_zone%dresult = dev_set_name(&tz->device, "thermal_zone%d", tz->id);if (result) {thermal_zone_destroy_device_groups(tz);goto remove_id;}// 初始化 thermal zone device,注册到 device model// 得到完整节点:sys/class/thermal/thermal_zone0 这种thermal_zone_device_init(tz);result = device_register(&tz->device);if (result)goto release_device;// 遍历 trips,如果 trip 无效或温度为 0,则禁用for (count = 0; count < num_trips; count++) {struct thermal_trip trip;result = thermal_zone_get_trip(tz, count, &trip);if (result || !trip.temperature)set_bit(count, &tz->trips_disabled);}/* Update 'this' zone's governor information */mutex_lock(&thermal_governor_lock);// 依据 struct thermal_zone_params *tzp 指定的 governor 或系统默认 governorif (tz->tzp)governor = __find_governor(tz->tzp->governor_name); // __find_governor 根据名称找到 governor 对象elsegovernor = def_governor;// 设置控制策略,之后 thermal zone 可以直接调用 governorresult = thermal_set_governor(tz, governor);if (result) {mutex_unlock(&thermal_governor_lock);goto unregister;}mutex_unlock(&thermal_governor_lock);// 注册 hwmon 接口if (!tz->tzp || !tz->tzp->no_hwmon) {// 默认会在 /sys/class/hwmon/ 下导出温度传感器节点result = thermal_add_hwmon_sysfs(tz);if (result)goto unregister;}mutex_lock(&thermal_list_lock);mutex_lock(&tz->lock);// 将该 thermal zone 挂到全局 thermal_tz_listlist_add_tail(&tz->node, &thermal_tz_list); mutex_unlock(&tz->lock);mutex_unlock(&thermal_list_lock);/* Bind cooling devices for this zone */// 自动尝试绑定 cooling device。》》》》》》》》》 重要的!!!bind_tz(tz);// 初始化延迟工作队列 delayed_work,用于轮询温度。// tz->poll_queue 的回调函数是 thermal_zone_device_checkINIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);/* Update the new thermal zone and mark it as already updated. */// 第一次强制更新温度状态// 设置 thermal zone need_update=1if (atomic_cmpxchg(&tz->need_update, 1, 0))thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);// 通知用户空间 thermal zone 已创建thermal_notify_tz_create(tz->id, tz->type);return tz;// 按照分配顺序逆序清理,避免内存泄漏
unregister:device_del(&tz->device);
release_device:put_device(&tz->device);
remove_id:ida_free(&thermal_tz_ida, id);
free_tzp:kfree(tz->tzp);
free_tz:kfree(tz);return ERR_PTR(result);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);

【INIT_DELAYED_WORK】
INIT_DELAYED_WORK 对延迟队列对进行回调绑定,tz->poll_queue 的回调函数是 thermal_zone_device_check。

INIT_WORK(&(_work)->work, _func) 会把 _func 作为回调函数绑定到 work_struct 里。

#define __INIT_DELAYED_WORK(_work, _func, _tflags)			\do {								\INIT_WORK(&(_work)->work, (_func));			\__init_timer(&(_work)->timer,				\delayed_work_timer_fn,			\(_tflags) | TIMER_IRQSAFE);		\} while (0)#define INIT_DELAYED_WORK(_work, _func)					\__INIT_DELAYED_WORK(_work, _func, 0)

调用流程:

  • 检查参数(type、trips、ops 等合法性)
  • 分配并初始化 thermal_zone_device
  • 注册 sysfs 节点和 device
  • 初始化 trips、governor、hwmon
  • 加入全局链表
  • 绑定 cooling device 并开始轮询工作
  • 更新一次温度并通知用户空间

2.1.2 thermal_zone_create_device_groups

thermal_zone_create_device_groups() 是 thermal zone 注册过程里负责 生成 sysfs 属性组(attribute groups) 的函数。它的主要作用就是把标准属性(比如 temp, mode, type 等)和动态生成的 trip 属性(trip_point_*)一起挂到 thermal_zoneX 的 sysfs 目录下。

// path: drivers/thermal/thermal_sysfs.c
int thermal_zone_create_device_groups(struct thermal_zone_device *tz,int mask)
{const struct attribute_group **groups;int i, size, result;/* we need one extra for trips and the NULL to terminate the array */// thermal_zone_attribute_groups 是内核里定义好的 静态属性组数组(一组 sysfs 属性,始终存在// 一个位置预留给 trips 动态属性组(tz->trips_attribute_group),需要 +1// 一个位置预留给 NULL 结尾(sysfs 要求 groups 数组以 NULL 结尾,需要 +1size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2;/* This also takes care of API requirement to be NULL terminated */// 分配一个数组,每个元素指向 struct attribute_groupgroups = kcalloc(size, sizeof(*groups), GFP_KERNEL);if (!groups)return -ENOMEM;// 将标准属性组(如 temp, mode, policy 等)复制到 groups 前面部分。// 留下最后两个槽位:一个给 trips,一个 NULfor (i = 0; i < size - 2; i++)groups[i] = thermal_zone_attribute_groups[i];// 创建 trip 属性组if (tz->num_trips) {result = create_trip_attrs(tz, mask);if (result) {kfree(groups);return result;}// 成功后,将该 trips属性组 挂到 groups[size - 2]groups[size - 2] = &tz->trips_attribute_group;}// 把 groups 挂到 tz->device.groups,这样在 device_register() 时,sysfs 会自动创建这些属性tz->device.groups = groups;return 0;
}

进一步看下 create_trip_attrs 函数。

// path: drivers/thermal/thermal_sysfs.c/*** create_trip_attrs() - create attributes for trip points* @tz:		the thermal zone device* @mask:	Writeable trip point bitmap.*  * helper function to instantiate sysfs entries for every trip* point and its properties of a struct thermal_zone_device.*  * Return: 0 on success, the proper error value otherwise.*/
static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
{struct attribute **attrs;int indx;/* This function works only for zones with at least one trip */// 检查是否有 tripif (tz->num_trips <= 0)return -EINVAL;// 分配三类属性数组// step 1: 分配数组 typetz->trip_type_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_type_attrs),GFP_KERNEL);if (!tz->trip_type_attrs)return -ENOMEM;// step 2: 分配数组 temptz->trip_temp_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_temp_attrs),GFP_KERNEL);// 失败,需要回滚清理之前的内存分配if (!tz->trip_temp_attrs) {kfree(tz->trip_type_attrs);return -ENOMEM;}// step 3: 分配数组 hysttz->trip_hyst_attrs = kcalloc(tz->num_trips,sizeof(*tz->trip_hyst_attrs),GFP_KERNEL);// 失败,需要回滚清理之前的内存分配if (!tz->trip_hyst_attrs) {kfree(tz->trip_type_attrs);kfree(tz->trip_temp_attrs);return -ENOMEM;}// 每个 trip 有 3 个属性,因此分配 num_trips * 3 + 1 个 struct attribute 指针// 末尾留一个 NULL,sysfs 需要 NULL 结尾的属性数组attrs = kcalloc(tz->num_trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);if (!attrs) {kfree(tz->trip_type_attrs);kfree(tz->trip_temp_attrs);kfree(tz->trip_hyst_attrs);return -ENOMEM;}for (indx = 0; indx < tz->num_trips; indx++) {/* create trip type attribute */// trip_point_%d_type 节点snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,"trip_point_%d_type", indx);sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);tz->trip_type_attrs[indx].attr.attr.name =tz->trip_type_attrs[indx].name; // 名字:trip_point_%d_typetz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO; // 权限:S_IRUGO(只读)tz->trip_type_attrs[indx].attr.show = trip_point_type_show; // show 回调:trip_point_type_show()attrs[indx] = &tz->trip_type_attrs[indx].attr.attr; // 挂到 attrs[indx]/* create trip temp attribute */// trip_point_%d_temp 节点snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,"trip_point_%d_temp", indx);sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);tz->trip_temp_attrs[indx].attr.attr.name =tz->trip_temp_attrs[indx].name;tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO; // 默认只读tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show; // 回调:trip_point_temp_show()(显示温度值if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&mask & (1 << indx)) {// 如果 CONFIG_THERMAL_WRITABLE_TRIPS 打开且 mask 对应位为 1 → 变成可写 0644tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;tz->trip_temp_attrs[indx].attr.store =trip_point_temp_store; // 回调(可选):trip_point_temp_store()(允许写入修改 trip 温度阈值)}attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr; // 挂到 attrs[indx + num_trips]// trip_point_%d_hyst 节点// 操作基本一样snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,"trip_point_%d_hyst", indx);sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);tz->trip_hyst_attrs[indx].attr.attr.name =tz->trip_hyst_attrs[indx].name;tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;if (tz->ops->set_trip_hyst) {tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;tz->trip_hyst_attrs[indx].attr.store =trip_point_hyst_store;}attrs[indx + tz->num_trips * 2] =&tz->trip_hyst_attrs[indx].attr.attr; // 挂到 attrs[indx + num_trips*2]}attrs[tz->num_trips * 3] = NULL; // 设置 NULL 结尾。sysfs 属性数组必须以 NULL 结尾// 将属性数组挂到 tz->trips_attribute_group,供上层 thermal_zone_create_device_groups() 使用tz->trips_attribute_group.attrs = attrs;return 0;
}

最终效果如下:
在这里插入图片描述
【总结】
create_trip_attrs() 为每个 trip 动态创建 sysfs 属性:

  • type:trip 类型(always read-only)
  • temp:trip 温度(只读或可写,取决于配置和 mask)
  • hyst:滞回值(只读或可写,取决于驱动是否实现 set_trip_hyst)

最终挂到 tz->trips_attribute_group,然后再由 thermal_zone_create_device_groups() 放入 tz->device.groups,在 sysfs 节点里暴露。

2.1.3 thermal_notify_tz_create

thermal_notify_tz_create() 和 thermal_genl_send_event() 两个函数,它们主要负责 thermal 子系统通过 Generic Netlink 向用户空间广播事件。

当 thermal zone(热区)被创建时,调用此函数,通过 netlink 向用户态发送 THERMAL_GENL_EVENT_TZ_CREATE 事件。

//path: drivers/thermal/thermal_netlink.c
int thermal_notify_tz_create(int tz_id, const char *name)
{struct param p = { .tz_id = tz_id, .name = name };return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE, &p);
}/** Generic netlink event encoding*/
static int thermal_genl_send_event(enum thermal_genl_event event,struct param *p)
{struct sk_buff *msg;int ret = -EMSGSIZE;void *hdr;// 申请一个 sk_buff,作为 netlink 消息缓冲区msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);if (!msg)return -ENOMEM;p->msg = msg;// 添加 Generic Netlink 消息头// thermal_gnl_family 是 thermal 子系统的 netlink family,注册时定义了 family 名称、id 等// event 指定消息的命令码hdr = genlmsg_put(msg, 0, 0, &thermal_gnl_family, 0, event);if (!hdr)goto out_free_msg;// 调用对应事件的编码回调函数,将 thermal zone 的信息写入 netlink 消息ret = event_cb[event](p);if (ret)goto out_cancel_msg;// 设置 netlink 消息尾部,修正消息长度genlmsg_end(msg, hdr);// 将消息广播给所有订阅 thermal generic netlink 组的用户进程// 用户空间可以通过 genetlink socket 接收 thermal 事件,比如 tz create/destroy, trip trigger 等genlmsg_multicast(&thermal_gnl_family, msg, 0, 1, GFP_KERNEL);return 0;out_cancel_msg:genlmsg_cancel(msg, hdr);
out_free_msg:nlmsg_free(msg);return ret;
}

当内核创建一个新的 thermal zone 时,用户空间会收到一个 netlink 事件通知,携带该 zone 的 id 和 name。

2.1.4 归纳梳理

(1)得到了什么?—— thermal zone 对象

整个注册过程,从 thermal_sensor 注册 thermal zone 对象开始,始终都是要返回一个 thermal_zone_device * 指针。因此,后续对 thermal zone 的操作都是基于这个指针对象来进行的。去掉不必要的信息,简化如下:

// 用个指针指向这样一个结构体对象,装载众多信息
struct thermal_zone_device {...// 通过这个thermal zone函数回调集合,能够轻松通过 ops 指针这个执行想要的行为。// 这就是指针的遍历。struct thermal_zone_device_ops *ops;// thermal zone的一些参数,随用随取 struct thermal_zone_params *tzp;// 绑定一个 governor 对象,通过thermal zone就能找到自己的governor,不乱不错很迅速struct thermal_governor *governor;...
};
(2)thermal_zone ops 回调绑定

thermal_zone_device_ops 对应这一堆回调函数,在不同的地方,搞一个 “tz->ops->xxx” 就开始调用了。下面就把这梳理以下,都是什么回调:

  • ops->bind / ops->unbind
 static struct thermal_zone_device *thermal_of_zone_register(struct device_node *sensor, int id, void *data, const struct thermal_zone_device_ops *ops) 
{of_ops->bind = thermal_of_bind;of_ops->unbind = thermal_of_unbind;
}
  • ops->get_temp / ops->set_trips
    将 rockchip_of_thermal_ops 作为参数传入devm_thermal_of_zone_register。
static const struct thermal_zone_device_ops rockchip_of_thermal_ops = {.get_temp = rockchip_thermal_get_temp,.set_trips = rockchip_thermal_set_trips,
};
  • ops->critical
struct thermal_zone_device *
thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *trips,int num_trips, int mask,void *devdata, struct thermal_zone_device_ops *ops,const struct thermal_zone_params *tzp, int passive_delay,int polling_delay)
{if (!ops->critical)ops->critical = thermal_zone_device_critical;
}

2.2 cooling device 注册

以 devfreq cooling 注册来看,如下:

devfreq_cooling_register
↓
of_devfreq_cooling_register_power
↓
thermal_of_cooling_device_register

2.2.1 thermal_of_cooling_device_register

注册一个 cooling device(冷却设备,例如 CPUFreq、GPUIfreq、风扇等)到 thermal framework,使其可以被 thermal zone 调度使用。

/*** thermal_of_cooling_device_register() - register an OF thermal cooling device* @np:		a pointer to a device tree node.* @type:	the thermal cooling device type.* @devdata:	device private data.* @ops:		standard thermal cooling devices callbacks.** This function will register a cooling device with device tree node reference.* This interface function adds a new thermal cooling device (fan/processor/...)* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself* to all the thermal zone devices registered at the same time.** Return: a pointer to the created struct thermal_cooling_device or an* ERR_PTR. Caller must check return value with IS_ERR*() helpers.*/
struct thermal_cooling_device *
thermal_of_cooling_device_register(struct device_node *np,const char *type, void *devdata,const struct thermal_cooling_device_ops *ops)
{return __thermal_cooling_device_register(np, type, devdata, ops);
}
EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);

thermal_of_cooling_device_register做一次外部封装,EXPORT_SYMBOL_GPL 说明这是 GPL-only 内核导出符号,驱动开发者可直接调用。

实际实现注册功能的函数是 __thermal_cooling_device_register
参数说明:

  • struct device_node *np → cooling device 对应的设备树节点(可能为 NULL)。
  • char *type → cooling device 类型字符串(如 “cpufreq”, “gpu”, “fan”)。
  • void *devdata → 驱动传入的私有数据指针(比如 CPU 的 policy、GPU 的控制句柄)。
  • struct thermal_cooling_device_ops *ops → cooling device 的操作函数集(必须提供 get_max_state、get_cur_state、set_cur_state)。
/*** __thermal_cooling_device_register() - register a new thermal cooling device* @np:		a pointer to a device tree node.* @type:	the thermal cooling device type.* @devdata:	device private data.* @ops:		standard thermal cooling devices callbacks.** This interface function adds a new thermal cooling device (fan/processor/...)* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself* to all the thermal zone devices registered at the same time.* It also gives the opportunity to link the cooling device to a device tree* node, so that it can be bound to a thermal zone created out of device tree.** Return: a pointer to the created struct thermal_cooling_device or an* ERR_PTR. Caller must check return value with IS_ERR*() helpers.*/
static struct thermal_cooling_device *
__thermal_cooling_device_register(struct device_node *np,const char *type, void *devdata,const struct thermal_cooling_device_ops *ops)
{struct thermal_cooling_device *cdev; // cooling device 对象struct thermal_zone_device *pos = NULL; // thermal zone,更建议改一下名字,易混淆。int id, ret;// 校验 ops,至少要实现 3 个核心方法:// get_max_state() → 最大冷却级别。get_cur_state() → 当前状态。set_cur_state() → 设置状态。if (!ops || !ops->get_max_state || !ops->get_cur_state ||!ops->set_cur_state)return ERR_PTR(-EINVAL);if (!thermal_class)return ERR_PTR(-ENODEV);// 分配一个 struct thermal_cooling_device 结构体cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);if (!cdev)return ERR_PTR(-ENOMEM);// 使用 IDA 分配唯一 ID(如 0, 1, 2...),给 cooling device 命名时使用ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);if (ret < 0)goto out_kfree_cdev;cdev->id = ret;id = ret;// cdev->type → cooling device 类型字符串cdev->type = kstrdup(type ? type : "", GFP_KERNEL);if (!cdev->type) {ret = -ENOMEM;goto out_ida_remove;}mutex_init(&cdev->lock);// thermal_instances 链表(存储它和各个 thermal zone 的绑定关系INIT_LIST_HEAD(&cdev->thermal_instances);// 设置 ops、设备树节点、devdata等cdev->np = np;cdev->ops = ops;cdev->updated = false;cdev->device.class = thermal_class;cdev->devdata = devdata;// 调用驱动提供的 get_max_state() 填充最大状态值ret = cdev->ops->get_max_state(cdev, &cdev->max_state);if (ret)goto out_cdev_type;// 在 /sys/class/thermal/cooling_deviceX/ 下创建属性文件节点thermal_cooling_device_setup_sysfs(cdev);// 给 device 命名,比如 "cooling_device0"ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);if (ret)goto out_cooling_dev;// 注册到内核设备模型,挂到 /sys/devices/virtual/thermal/ret = device_register(&cdev->device);if (ret) {/* thermal_release() handles rest of the cleanup */put_device(&cdev->device);return ERR_PTR(ret);}/* Add 'this' new cdev to the global cdev list */mutex_lock(&thermal_list_lock);// 把新注册的 cooling device 加入全局链表 thermal_cdev_listlist_add(&cdev->node, &thermal_cdev_list);/* Update binding information for 'this' new cdev */// bind_cdev(cdev) 会尝试将该 cdev 与已有的 thermal zone 绑定// 》》》》》》》》》 重要的!!!bind_cdev(cdev);// 遍历全局 thermal_tz_list,已注册的 thermal zones。从thermal zone列表中找。list_for_each_entry(pos, &thermal_tz_list, node)// 如果某个 thermal zone 需要更新(need_update=1),就调用 thermal_zone_device_update()// thermal zone 设置这个值,这里可以用。if (atomic_cmpxchg(&pos->need_update, 1, 0))// 重新检查温度并可能重新分配冷却策略thermal_zone_device_update(pos,THERMAL_EVENT_UNSPECIFIED);mutex_unlock(&thermal_list_lock);return cdev;out_cooling_dev:thermal_cooling_device_destroy_sysfs(cdev);
out_cdev_type:kfree(cdev->type);
out_ida_remove:ida_free(&thermal_cdev_ida, id);
out_kfree_cdev:kfree(cdev);return ERR_PTR(ret);
}

【总结流程】

  • 校验 ops → 必须支持 get_max_state/get_cur_state/set_cur_state。
  • 分配 cooling device 结构体和 ID。
  • 设置类型、ops、devdata。
  • 查询最大状态值。
  • 创建 sysfs 节点。
  • 注册到内核设备模型,出现在 /sys/class/thermal/cooling_deviceX。
  • 加入全局 thermal_cdev_list,尝试绑定到已有 thermal zone。
  • 通知所有 thermal zone 更新策略。

2.2.2 thermal_cooling_device_setup_sysfs

cooling device 注册过程中 给 sysfs 准备属性文件 的关键逻辑。

void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev)
{cooling_device_stats_setup(cdev);cdev->device.groups = cooling_device_attr_groups;
}

主要工作:

  • 调用 cooling_device_stats_setup(cdev):为该 cooling device 设置统计结构体,并准备统计类的sysfs 属性。
  • 设置 cdev->device.groups:将属性组数组挂到 cdev->device,这样 device_register() 时,sysfs 会自动在 /sys/class/thermal/cooling_deviceX/ 下创建这些文件。

核心的函数实现,如下:

static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
{// 指向统计信息的 sysfs 属性组const struct attribute_group *stats_attr_group = NULL;struct cooling_dev_stats *stats;/* Total number of states is highest state + 1 */// cooling device 的状态数(最大状态 + 1)unsigned long states = cdev->max_state + 1;int var;// 计算分配内存的大小var = sizeof(*stats); // struct cooling_dev_stats 本体大小var += sizeof(*stats->time_in_state) * states; // 每个状态停留的时间var += sizeof(*stats->trans_table) * states * states; // 状态迁移表,统计从某个状态切换到另一个状态的次数stats = kzalloc(var, GFP_KERNEL);if (!stats)goto out;stats->time_in_state = (ktime_t *)(stats + 1); // 指向 stats 结构体后面的数组stats->trans_table = (unsigned int *)(stats->time_in_state + states); // trans_table 紧接着 time_in_state 数组cdev->stats = stats; // 把统计结构挂到 cooling devicestats->last_time = ktime_get(); // 记录当前时间,便于后续计算停留时间spin_lock_init(&stats->lock);// 指向 cooling_device_stats_attr_group,说明要给 sysfs 增加统计相关的属性组stats_attr_group = &cooling_device_stats_attr_group;out:/* Fill the empty slot left in cooling_device_attr_groups */// cooling_device_attr_groups 是 cooling device 的 sysfs 属性组数组// 数组里预留了一个空位(倒数第 2 个位置),专门用来放统计信息属性组var = ARRAY_SIZE(cooling_device_attr_groups) - 2;cooling_device_attr_groups[var] = stats_attr_group;
}

2.2.3 归纳梳理

thermal_cooling_device 注册完成,得到thermal_cooling_device 类型的指针。最重要的是在thermal_cooling_device 有一个 const struct thermal_cooling_device_ops *ops 成员。后续,通过thermal zone 找到 cooling device 就可以拿到 thermal_cooling_device_ops 。

struct thermal_cooling_device *
of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,struct devfreq_cooling_power *dfc_power)
{...ops = &dfc->cooling_ops;ops->get_max_state = devfreq_cooling_get_max_state;ops->get_cur_state = devfreq_cooling_get_cur_state;ops->set_cur_state = devfreq_cooling_set_cur_state;em = em_pd_get(dev);if (em && !em_is_artificial(em)) {dfc->em_pd = em;ops->get_requested_power =devfreq_cooling_get_requested_power;ops->state2power = devfreq_cooling_state2power;ops->power2state = devfreq_cooling_power2state;dfc->power_ops = dfc_power;num_opps = em_pd_nr_perf_states(dfc->em_pd);}...
}

同样,governor也可以更好操作 thermal_cooling_device_ops。后面在 governor 中也能找到 这两者的关系。

2.3 governor 注册

governor 注册过程:

thermal_init // thermal core 初始化函数
↓
thermal_register_governors
↓
thermal_register_governor

2.3.1 __init thermal_register_governors

__init:这是内核初始化段函数(only used during init):执行完后其内存可被回收。该函数通常在内核启动或模块初始化里被调用。

static int __init thermal_register_governors(void)
{int ret = 0;struct thermal_governor **governor; // *(*governor) 指针的指针,这样好理解。可以表示一个thermal_governor 数组了。// for_each_governor_table 迭代注册表中每个 governor 条目// governor 指向表中当前元素的地址, *governor 是当前(struct thermal_governor *)指针对象// 二级指针,可以接把迭代变量表示为数组元素的地址。然后*governor就是我们要的thermal_governor型指针了。for_each_governor_table(governor) {ret = thermal_register_governor(*governor); // 调用注册函数if (ret) {// ret > 0,表示注册失败,能拿到一个 governor 地址pr_err("Failed to register governor: '%s'",(*governor)->name); // 打印失败 governor 名字break;}pr_info("Registered thermal governor '%s'",(*governor)->name); // 打印成功 governor 名字}if (ret) {struct thermal_governor **gov;// 之前保存了失败 governor 的地址,因为变量命名问题,导致地址变量名与Governor策略对象容易混淆。// 遍历注册表,有机会对所有的 Governor 遍历一下for_each_governor_table(gov) {// 匹配一下失败的地址,是不是当前的遍历位置。通俗的说,背锅到人。if (gov == governor)break;// 不是有问题的 governor,释放后就可以通过了。thermal_unregister_governor(*gov);}}return ret;
}

需要注意,for_each_governor_table 这个宏定义如下:

// path: drivers/thermal/thermal_core.h
#define for_each_governor_table(__governor)		\for (__governor = __governor_thermal_table;	\__governor < __governor_thermal_table_end;	\__governor++)

2.3.2 thermal_register_governor

单个 governor 的注册函数。负责把 governor 插入全局 governor 列表、设定默认 governor(如果匹配),并为当前系统中未设 governor 但在 tzp 中指定了 governor_name 的 thermal zone 分配该 governor。

int thermal_register_governor(struct thermal_governor *governor)
{int err;const char *name;struct thermal_zone_device *pos; // 单个 thermal zone 对象// 有效性判断if (!governor)return -EINVAL;mutex_lock(&thermal_governor_lock);err = -EBUSY;// 根据governor的名称检索是否存在if (!__find_governor(governor->name)) {bool match_default;err = 0;// 将此 governor 加入内核链表 thermal_governor_list// list_add 是将元素插入到链表头(LIFO);因此注册顺序与链表顺序会倒置,后来居上的倒序表。list_add(&governor->governor_list, &thermal_governor_list);// 用 strncmp(区分大小写)比较前 THERMAL_NAME_LENGTH 字符是否与预定义默认名相等// strncmp 返回 0 表示相等match_default = !strncmp(governor->name,DEFAULT_THERMAL_GOVERNOR,THERMAL_NAME_LENGTH);// 符合要求,就设置该governor作为默认的governorif (!def_governor && match_default)def_governor = governor;}mutex_lock(&thermal_list_lock);// 遍历 thermal zone 列表list_for_each_entry(pos, &thermal_tz_list, node) {/** only thermal zones with specified tz->tzp->governor_name* may run with tz->govenor unset*/// 如果 zone 已经有 governor(即已经被设置),跳过,不做覆盖if (pos->governor)continue;// thermal_zone_device -> thermal_zone_params -> char governor_name[THERMAL_NAME_LENGTH]// 获取名字name = pos->tzp->governor_name;// thermal_zone 的 governor 与当前 governor 的名字进行比较if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {// 若相等(strncasecmp 返回 0),配对成功,开始搞事。int ret;// 调用 thermal_set_governor() 将该 zone 的 governor 设置为当前注册进来的 governorret = thermal_set_governor(pos, governor);if (ret)dev_err(&pos->device,"Failed to set governor %s for thermal zone %s: %d\n",governor->name, pos->type, ret);}}mutex_unlock(&thermal_list_lock);mutex_unlock(&thermal_governor_lock);return err;
}

调用 thermal_set_governor() 要求 thermal zone 配置的 governor 名字(字符串)必须和注册进来的 governor 的名字匹配,才会进行绑定。否则不会调用 thermal_set_governor()。

thermal zone 的 governor 必须和注册 governor 的名字匹配,否则不会自动设置 governor。thermal zone 的 governor 名称是在 zone 创建时就指定的(比如从设备树里读取 governor-name 字段)。系统启动时,内核会遍历所有注册 governor,把它们和 thermal zone 的配置名字做匹配,只有名字一致的才会被绑定。

【举例】

cpu_thermal: cpu-thermal {polling-delay-passive = <250>;polling-delay = <1000>;thermal-sensors = <&cpu_sensor 0>;sustainable-power = <2000>;governor-name = "power_allocator";
};

当 power_allocator governor 注册时,thermal_register_governor(“power_allocator”) 会遍历所有 thermal zone,找到 cpu-thermal 的 governor_name == “power_allocator”,就会执行 thermal_set_governor(cpu_thermal, power_allocator)。
如果其他的 zone 如果写了 “step_wise”,就只能等 step_wise governor 注册时才会匹配成功。

2.3.3 thermal_set_governor

把某个 thermal zone 绑定到一个新的 thermal governor 上,也就是给这个温控区域选择具体的调节策略。

参数说明:

  • tz: struct thermal_zone_device *,代表一个 thermal zone(温控区域,比如 CPU、GPU的温度监控点)。
  • new_gov: struct thermal_governor *,新的 governor 策略(如 step_wise、fair_share、user_space、power_allocator 等)。
/*** thermal_set_governor() - Switch to another governor* @tz:		a valid pointer to a struct thermal_zone_device* @new_gov:	pointer to the new governor** Change the governor of thermal zone @tz.** Return: 0 on success, an error if the new governor's bind_to_tz() failed.*/
static int thermal_set_governor(struct thermal_zone_device *tz,struct thermal_governor *new_gov)
{int ret = 0; // 返回值,初始化为 0,表示默认成功// 旧 governor 的解绑if (tz->governor && tz->governor->unbind_from_tz)// governor 提供了 unbind_from_tz 回调函数// 以 power_allocator 为例就是 power_allocator_unbindtz->governor->unbind_from_tz(tz);// 新 governor 的绑定if (new_gov && new_gov->bind_to_tz) {// 以 power_allocator 为例就是 power_allocator_bindret = new_gov->bind_to_tz(tz);if (ret) {// 调用 bind_previous_governor(tz, new_gov->name) 尝试回退(重新绑定到之前的 governor,保证系统不丧失控制能力)bind_previous_governor(tz, new_gov->name);return ret;}}// 如果绑定成功,就更新 tz->governor 指针,表示 thermal zone 已经切换到新的 governortz->governor = new_gov;return ret;
}

继续看下 bind_previous_governor:

/*** bind_previous_governor() - bind the previous governor of the thermal zone* @tz:		a valid pointer to a struct thermal_zone_device* @failed_gov_name:	the name of the governor that failed to register** Register the previous governor of the thermal zone after a new* governor has failed to be bound.*/
static void bind_previous_governor(struct thermal_zone_device *tz,const char *failed_gov_name)
{if (tz->governor && tz->governor->bind_to_tz) {// 以 power_allocator 为例就是 power_allocator_bindif (tz->governor->bind_to_tz(tz)) {dev_err(&tz->device,"governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",failed_gov_name, tz->governor->name, tz->type);tz->governor = NULL;	`}}
}

2.3.4 归纳梳理

注册时序如下图:
在这里插入图片描述

【问】
如果 tz->tzp->governor_name 配置了一个不存在的 governor 名称,会发生什么情况?

① thermal zone 创建时(thermal_zone_device_register())当一个 thermal zone 被注册时,会把 tzp->governor_name 赋值到 tz->tzp->governor_name。此时并不会立刻检查这个名字是否存在对应的 governor。

② governor 注册流程(thermal_register_governor())注册 governor 时,会尝试去匹配还没有绑定 governor 的 thermal zone。如果 tzp->governor_name 写的是一个不存在的名字(比如 “foobar”),那么在 governor 注册过程中 不会有任何 governor 名称匹配成功,因此 tz->governor 仍然是 NULL。

③ 只有明确指定 governor 名称的 zone,才可能出现 governor 为空的情况。thermal 框架的核心是定时轮询传感器温度,然后通过 governor 决定 cooling device 的限制级别。如果 tz->governor == NULL,那这个 zone 就无法做出调节动作,等于“监控但不干预”。

④ 在 thermal_register_governor() 中match_default = !strncmp(governor->name, DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH) 如果 tzp->governor_name 没写(为空) → 内核会尝试用 def_governor 来填充。有可能在编译时定义的 DEFAULT_THERMAL_GOVERNOR(通常是 “step_wise”,需要找代码的)标记为全局的默认 governor。

⑤ 实际运行效果:thermal zone 会继续运行,传感器还能上报温度。但由于没有 governor 绑定,调节 cooling device 的环节失效,相当于“监控但不控温”。内核日志不会报错,只会静悄悄地没有 governor。

2.4 模块关系

对于 thermal zone、cooling device 和 thermal governor 的关系逻辑确定,必须要关注到 thermal_instance,结合这个结构体,我们继续分析:

2.4.1 thermal_instance

从结构体注释看:
thermal_zone_device 负责监测温度(温度传感器)。
thermal_cooling_device 负责降温(CPU/GPU 降频、风扇、限速)。
thermal_instance 负责把二者在一个 trip point 上绑定起来,并且携带这个绑定关系的上下文信息。

/** This structure is used to describe the behavior of* a certain cooling device on a certain trip point* in a certain thermal zone*/
struct thermal_instance {int id; // 唯一 ID,在内核中分配,主要用于区分不同 instancechar name[THERMAL_NAME_LENGTH]; // 名称,通常由 zone 类型 + cdev 类型 + trip 点信息拼接而成,用于调试和 sysfs 命名struct thermal_zone_device *tz; // 指向 thermal zone。说明这个 instance 是属于哪个 thermal zone 的。struct thermal_cooling_device *cdev; // 指向 cooling device。说明这个 instance 控制的具体冷却设备是谁。const struct thermal_trip *trip; // trip 点描述。即 “在某个温度阈值触发时执行的 cooling 策略”。bool initialized; // 表示该 instance 是否已经初始化完成// upper = 最大可用的 cooling stateunsigned long upper;	/* Highest cooling state for this trip point */// lower = 最小可用的 cooling stateunsigned long lower;	/* Lowest cooling state for this trip point */// governor 计算出的 目标 cooling stateunsigned long target;	/* expected cooling state */// sysfs 的 权重属性char attr_name[THERMAL_NAME_LENGTH];struct device_attribute attr;char weight_attr_name[THERMAL_NAME_LENGTH];struct device_attribute weight_attr;// 把这个 instance 挂到 thermal zone 的 tz->thermal_instances 链表里struct list_head tz_node; /* node in tz->thermal_instances */// 把这个 instance 挂到 cooling device 的 cdev->thermal_instances 链表里struct list_head cdev_node; /* node in cdev->thermal_instances */// 权重值(相对值),用于 governor 在多 cooling device 下分配 cooling target 时的比例因子unsigned int weight; /* The weight of the cooling device */bool upper_no_limit;
};

thermal_instance 就是一个 “绑定关系”,定义:在某个 trip 上,把某个 cooling device 绑定进来,并带上上下限、权重、target state 等运行时数据。

最终,governor 在每次温度超阈时,会遍历 tz->thermal_instances,算出每个 instance 的 target,再调用对应 cdev 的 .set_cur_state()。

2.4.2 bind list

thermal zone、cooling device 通过 thermal_instances 建立对象list,进一步看下这些list怎么组成,怎样添加数据。

(1)list_head

① struct list_head
内核的双向循环链表通用头,定义很短。

// path:include/linux/types.h。
struct list_head {struct list_head *next, *prev;
};

这是一个双向且环形(circular)的链表实现:链表头本身也是一个 struct list_head 节点,空表时 head->next == head 且 head->prev == head。把 struct list_head 放到其它结构体里,使那个结构体能被挂到相应的链表上。

为什么要这样设计?
Linux 的链表是 环形双向链表。链表头不是“哑节点”,而是真正的节点,只是它不存放业务数据。

② INIT_LIST_HEAD

// path: include/linux/list.h
/*** INIT_LIST_HEAD - Initialize a list_head structure* @list: list_head structure to be initialized.** Initializes the list_head to point to itself.  If it is a list header,* the result is an empty list.*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{WRITE_ONCE(list->next, list); // 宏 WRITE_ONCE(x, val) 相当于 list->next = list;WRITE_ONCE(list->prev, list); // 相当于 list->prev = list;
}

初始化后的结构,假设 struct list_head head;,执行 INIT_LIST_HEAD(&head) 后:
head->next == head,head->prev == head 表示这是一个空链表。
关键定义:

// drivers/thermal/thermal_core.cthermal_core.c:892:     INIT_LIST_HEAD(&cdev->thermal_instances);
thermal_core.c:1299:    INIT_LIST_HEAD(&tz->thermal_instances);
thermal_core.c:1300:    INIT_LIST_HEAD(&tz->node);

③ list_add
插入到 head 之后(链表头部插入)。

/** Insert a new entry between two known consecutive entries.** This is only for internal list manipulation where we know* the prev/next entries already!*/
static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
{if (!__list_add_valid(new, prev, next))return;next->prev = new;new->next = next;new->prev = prev;WRITE_ONCE(prev->next, new);
}/*** list_add - add a new entry* @new: new entry to be added* @head: list head to add it after** Insert a new entry after the specified head.* This is good for implementing stacks.*/
static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}

④ list_for_each_entry
正向遍历(不在循环中删除当前元素)。

/*** list_for_each_entry	-	iterate over list of given type* @pos:	the type * to use as a loop cursor.* @head:	the head for your list.* @member:	the name of the list_head within the struct.*/
#define list_for_each_entry(pos, head, member)				\for (pos = list_first_entry(head, typeof(*pos), member);	\!list_entry_is_head(pos, head, member);			\pos = list_next_entry(pos, member))

⑤ LIST_HEAD

/** Circular doubly linked list implementation.** Some of the internal functions ("__xxx") are useful when* manipulating whole lists rather than single entries, as* sometimes we already know the next/prev entries and we can* generate better code by using them directly rather than* using the generic single-entry routines.*/#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)

重要的 thermal list,都是全局定义

// drivers/thermal/thermal_core.cstatic LIST_HEAD(thermal_tz_list); // thermal zone 列表
static LIST_HEAD(thermal_cdev_list); // cooling device 列表
static LIST_HEAD(thermal_governor_list); // thermal governor 列表
(2)bind_tz

bind_tz() 的作用是:当一个新的 thermal zone 注册时,尝试把它绑定到系统中所有已存在的 cooling device。

static void bind_tz(struct thermal_zone_device *tz)
{int ret;struct thermal_cooling_device *pos = NULL;// 判断thermal zone的ops中是否有绑定bind函数if (!tz->ops->bind)return;mutex_lock(&thermal_list_lock);// 遍历 全局 thermal_cdev_list(系统里注册的所有 thermal_cooling_device)list_for_each_entry(pos, &thermal_cdev_list, node) {// 执行 thermal zone 的 bind 操作,把遍历的 cdev(cooling device) 绑定到 tz(thermal zone)ret = tz->ops->bind(tz, pos); // 绑定关系if (ret)print_bind_err_msg(tz, pos, ret);}mutex_unlock(&thermal_list_lock);
}

tz->ops->bind 详解:

  • 获取 thermal_zone_device -> thermal_zone_device_ops
  • 执行 thermal_zone_device_ops -> bind = thermal_of_bind, thermal_of_zone_register 中 of_ops->bind = thermal_of_bind
(3)bind_cdev

bind_cdev() 的作用是:当一个新的 cooling device 注册时,尝试把它绑定到系统中所有已存在的 thermal zone。

static void bind_cdev(struct thermal_cooling_device *cdev)
{int ret;struct thermal_zone_device *pos = NULL;// 遍历 全局 thermal_tz_list(系统里注册的所有 thermal_zone_device)list_for_each_entry(pos, &thermal_tz_list, node) {if (pos->ops->bind) {// 执行 thermal zone 的 bind 操作,把传入的 cdev(cooling device) 绑定到 tz(thermal zone)ret = pos->ops->bind(pos, cdev); // 绑定关系if (ret)print_bind_err_msg(pos, cdev, ret);}}
}

调用的绑定函数都是 thermal_zone_device -> thermal_zone_device_ops -> bind。不同的是传入参数不同。这样,可以适配不同的使用场景。

2.4.3 关系梳理

(1)关系说明
简化一下几个模块的结构体定义,如下:

struct thermal_instance {...struct thermal_zone_device *tz; // 每个 thermal_instance 存一个关联的 thermal_zone_device struct thermal_cooling_device *cdev; // 每个 thermal_instance 存一个关联的 thermal_cooling_device const struct thermal_trip *trip;struct device_attribute weight_attr;/* node in tz->thermal_instances */struct list_head tz_node; // 给 thermal_zone_device 存储用的标识/* node in cdev->thermal_instances */struct list_head cdev_node; // 给 thermal_cooling_device 存储用的标识...
};struct thermal_cooling_device {...const struct thermal_cooling_device_ops *ops;struct list_head thermal_instances; // 一个链表,存着struct thermal_instance的cdev_node,这样就是一串tz_node,标识着不同的thermal_instance对象struct list_head node;
};struct thermal_zone_device {...struct thermal_zone_device_ops *ops;struct thermal_zone_params *tzp;struct thermal_governor *governor; // thermal_zone_device 绑定着的thermal_governorstruct list_head thermal_instances; // 一个链表,存着struct thermal_instance的tz_node,这样就是一串tz_node,标识着不同的thermal_instance对象struct list_head node;...
};struct thermal_governor {char name[THERMAL_NAME_LENGTH]; // thermal_governor 名称,能标识这个策略的,用于取数据int (*bind_to_tz)(struct thermal_zone_device *tz);void (*unbind_from_tz)(struct thermal_zone_device *tz);int (*throttle)(struct thermal_zone_device *tz, int trip);struct list_head	governor_list; // governor 策略实例的集合
};

归纳如下:

  • ① 这些结构体的list_head成员怎么初始化,前面有描述,不多阐述
  • ② thermal_zone_bind_cooling_device 中,list_add_tail(&dev->tz_node, &tz->thermal_instances) 将thermal_instance->tz_node,添加到 thermal_zone_device->thermal_instances 下面,串成链表
  • ③ thermal_zone_bind_cooling_device 中,list_add_tail(&dev->cdev_node, &cdev->thermal_instances) 将 thermal_instance->cdev_node,添加到 thermal_cooling_device->thermal_instances 下面,串成链表
  • ④ thermal_zone_bind_cooling_device 中,把 thermal_cooling_device、thermal_zone_device 赋值给 thermal_instance 的 *tz、*cdev。至此,达成三者桥接。
  • ⑤ thermal_zone_device 中有个 thermal_governor,这样三大模块关联在一起。

(2)通俗易懂解释
① thermal子系统这些结构体都是存放内存中的,每个变量值、变量结构体都是有地址的,互相包含也没关系,都是指针获取地址,不存在重复套娃定义,初始化后的对象地址不会错乱,无论怎么包含还是那个地址对应的对象。
② struct list_head 这些变量,涉及到相互挂在问题,看似难以理解,打个比方就跟小说里元婴修士在长老会挂职一样。在小团队中挂了职位,直接找到对应的负责人后,让具体的人把事情带走。大佬配上几个小弟,小弟需要到处填坑,毕竟大佬不需要干活,小弟需要干多分工作的。

套用到thermal系统就是:

  1. thermal子系统中,thermal_cooling_device,thermal_zone_device 都在 thermal_instance 挂个名,以后有事都可以找到 thermal_instance。
  2. thermal_cooling_device,thermal_zone_device 都可以通过 thermal_instance 找到对方,thermal_instance 是一个小弟,每个小弟有自己指定的 2 个分管大佬,不是随便乱改的。
  3. thermal_cooling_device,thermal_zone_device 都是大佬,底下必须要有一些小弟干活,struct list_head thermal_instances 一串小弟都在这里集合,花名册拿在手。
  4. 两个大佬 thermal_cooling_device,thermal_zone_device 碰面了,总归有大长老、二长老的分别,那么很简单thermal系统中,thermal_zone_device-大长老,thermal_cooling_device-二长老
  5. thermal_cooling_device,thermal_zone_device两个大佬,重要工作会——thermal_zone_bind_cooling_device。
  6. thermal_zone_device 找来自己所有的小弟【list_for_each_entry(pos, &tz->thermal_instances, tz_node)】,仔细筛选。发现有些人也是 thermal_cooling_device 的心腹【if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev)】,那还说啥,自己人放一边去。
  7. 但如果 thermal_zone_device 的小弟,不是 thermal_cooling_device 的小弟,赶紧派过去【list_add_tail(&dev->tz_node, &tz->thermal_instances);list_add_tail(&dev->cdev_node, &cdev->thermal_instances);】以后好办事。
  8. 最后发个广播【atomic_set(&tz->need_update, 1)】,小弟参与啥啥啥的工作,各方面注意。

3 温控管理逻辑

thermal 子系统通过轮询的方式,触发温度数据获取,来进行温控条件的判断并确认是否开始降温措施。接下来,就看下如何轮询温度、触发温控。

3.1 轮询机制

3.1.1 thermal_zone_device_check

通过 thermal_zone_device_register_with_trips 注册 thermal_zone 节点时,INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);

在delay时间结束时,可以回调 thermal_zone_device_check 函数。执行如下:

static void thermal_zone_device_check(struct work_struct *work)
{struct thermal_zone_device *tz = container_of(work, structthermal_zone_device,poll_queue.work);thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); // 执行thermal zone更新
}

3.1.2 thermal_zone_device_update

Linux thermal 子系统中核心的温度更新与调度函。

void thermal_zone_device_update(struct thermal_zone_device *tz,enum thermal_notify_event event)
{mutex_lock(&tz->lock);if (thermal_zone_is_present(tz)) // 确认 tz 对应的热区(thermal zone)是否仍然存在__thermal_zone_device_update(tz, event); mutex_unlock(&tz->lock);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_update);void __thermal_zone_device_update(struct thermal_zone_device *tz,enum thermal_notify_event event)
{int count;if (tz->suspended) // 检查是否 suspend。如果系统挂起 (suspend),则不再更新温度。return;// thermal zone 必须提供 get_temp() 回调,否则无法获取温度。if (WARN_ONCE(!tz->ops->get_temp,"'%s' must not be called without 'get_temp' ops set\n",__func__))return;// 检查 thermal zone 是否启用if (!thermal_zone_device_is_enabled(tz))return;//  更新温度,调用 tz->ops->get_temp() 获取当前温度。update_temperature(tz);// 设置 trip point__thermal_zone_set_trips(tz);tz->notify_event = event;// 遍历所有 trip point(临界温度点)for (count = 0; count < tz->num_trips; count++)handle_thermal_trip(tz, count); //逐个处理 trip 点// 启动/调整监控定时器,用于定期更新。保证 thermal zone 持续监控温度变化。monitor_thermal_zone(tz);
}

3.1.3 monitor_thermal_zone

monitor_thermal_zone 启动/调整监控定时器,用于定期更新。进一步调用 thermal_zone_device_set_polling 配合真延迟时间,进行轮询回调。

static void monitor_thermal_zone(struct thermal_zone_device *tz)
{if (tz->mode != THERMAL_DEVICE_ENABLED)thermal_zone_device_set_polling(tz, 0);else if (tz->passive)thermal_zone_device_set_polling(tz, tz->passive_delay_jiffies); // 高温轮询else if (tz->polling_delay_jiffies)thermal_zone_device_set_polling(tz, tz->polling_delay_jiffies); // 常规轮询
}static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,unsigned long delay)
{// 执行或不执行delay,之后执行操作(看有没有),再做回调tz->poll_queue,即回调thermal_zone_device_check if (delay)mod_delayed_work(system_freezable_power_efficient_wq,&tz->poll_queue, delay);elsecancel_delayed_work(&tz->poll_queue);
}

至此,轮询机制形成闭环。可以通过此方式,反复获取温度。

对于温度来源(update_temperature)和 温控临界点处理(handle_thermal_trip)下一步看。

3.2 温控触发

在轮询机制中,就调用 handle_thermal_trip 来处理所有遍历的 trip point。以此入口,逐步分析:

3.2.1 handle_thermal_trip

handle_thermal_trip 的职责是:

  • 判断当前温度是否触发了 trip point
  • 通知用户空间或内核其他部分
  • 调用合适的 cooling 策略

参数说明:
thermal_zone_device *tz:当前的 thermal zone(热区)。
int trip_id:trip point 索引。

static void handle_thermal_trip(struct thermal_zone_device *tz, int trip_id)
{struct thermal_trip trip; // 临时存放该 trip 的配置/* Ignore disabled trip points */// 检查该 trip 是否被禁用if (test_bit(trip_id, &tz->trips_disabled))return; // 如果该 trip 被禁用,直接退出。// 从 thermal zone 获取 trip 配置(温度阈值、hysteresis、类型等)。__thermal_zone_get_trip(tz, trip_id, &trip);// 无效温度,退出if (trip.temperature == THERMAL_TEMP_INVALID)return;// 判断上一次温度数据是否有效,需要判断温度变化趋势if (tz->last_temperature != THERMAL_TEMP_INVALID) {// 温度上升if (tz->last_temperature < trip.temperature &&tz->temperature >= trip.temperature)thermal_notify_tz_trip_up(tz->id, trip_id,tz->temperature);// 温度下降// hysteresis(滞后)防止温度在边界来回抖动,不断触发上下事件。if (tz->last_temperature >= trip.temperature &&tz->temperature < (trip.temperature - trip.hysteresis))thermal_notify_tz_trip_down(tz->id, trip_id,tz->temperature);}// 关键级 trip (THERMAL_TRIP_CRITICAL, THERMAL_TRIP_HOT)if (trip.type == THERMAL_TRIP_CRITICAL || trip.type == THERMAL_TRIP_HOT)handle_critical_trips(tz, trip_id, trip.temperature, trip.type);else// 非关键级 trip(如 PASSIVE、ACTIVE)handle_non_critical_trips(tz, trip_id);
}

3.2.2 handle_critical_trips

调用 handle_critical_trips() 通常是立即关机、重启、进入紧急保护状态(避免硬件损坏)。

static void handle_critical_trips(struct thermal_zone_device *tz,int trip, int trip_temp, enum thermal_trip_type trip_type)
{/* If we have not crossed the trip_temp, we do not care. */if (trip_temp <= 0 || tz->temperature < trip_temp)return;trace_thermal_zone_trip(tz, trip, trip_type);// 执行thermal_zone_device_ops的回调函数if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot)tz->ops->hot(tz);else if (trip_type == THERMAL_TRIP_CRITICAL)tz->ops->critical(tz);
}

其中 hot 回调没有看到绑定关系,但是 critical 的回调函数,直接指向 thermal_zone_device_critical,在thermal_zone_device_register_with_trips中绑定回调的(ops->critical = thermal_zone_device_critical)。继续,

void thermal_zone_device_critical(struct thermal_zone_device *tz)
{/** poweroff_delay_ms must be a carefully profiled positive value.* Its a must for forced_emergency_poweroff_work to be scheduled.*/int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;dev_emerg(&tz->device, "%s: critical temperature reached, ""shutting down\n", tz->type);// 执行高温关机,有个delay时间hw_protection_shutdown("Temperature too high", poweroff_delay_ms);
}
EXPORT_SYMBOL(thermal_zone_device_critical);

3.2.3 handle_non_critical_trips

handle_non_critical_trips 需要 governor 温控策略支持。

static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip)
{tz->governor ? tz->governor->throttle(tz, trip) :def_governor->throttle(tz, trip);
}

如果在注册过程,配置了thermal zone的专属governor,这里就会直接调用,如果没有,使用默认的那个。回调 thermal_governor -> int (*throttle)(struct thermal_zone_device *tz, int trip)。

3.2.4 温控执行

以 power_allocator 为例,就回调 power_allocator_throttle 函数,这是在 thermal_gov_power_allocator 添加到平台就绑定好的。接下来就是 power_allocator_throttle 的调用链了,之前有所分析,如下图:
在这里插入图片描述
简单看下这条调用逻辑:

power_allocator_throttle
↓
allow_maximum_power
↓
↓list_for_each_entry(instance, &tz->thermal_instances, tz_node)
↓
thermal_cooling_device *cdev = instance->cdev
↓
cdev->ops->get_requested_power(cdev, &req_power)
↓
cpufreq_cooling: cpufreq_get_requested_power

由此,thermal zone -> cooling device 的温控调用完成一次。

3.3 辅助函数

在上面的过程分析中,部分调用函数,集中在这看一下:

3.3.1 update temperature过程

(1)update_temperature

update_temperature 是 thermal zone 更新当前温度的关键函数。它的任务就是:

  • 调用驱动获取温度
  • 更新 thermal zone 的温度缓存字段
  • 记录 trace 信息
  • 通知用户空间(netlink)
static void update_temperature(struct thermal_zone_device *tz)
{int temp, ret;// 调用 __thermal_zone_get_temp() 从底层获取温度。ret = __thermal_zone_get_temp(tz, &temp);if (ret) {if (ret != -EAGAIN)dev_warn(&tz->device,"failed to read out thermal zone (%d)\n",ret);return;}// 读取成功后,更新刚刚读取到的温度值,保存之前温度值作为last,用于比较温度趋势tz->last_temperature = tz->temperature;tz->temperature = temp;// 记录 tracepoint,用于 ftrace/perf 等内核跟踪trace_thermal_temperature(tz);// 通过 generic netlink 向用户空间发送温度采样数据thermal_genl_sampling_temp(tz->id, temp);
}

进一步向下分析:

(2)__thermal_zone_get_temp
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{int ret = -EINVAL;int count;int crit_temp = INT_MAX;struct thermal_trip trip;lockdep_assert_held(&tz->lock);// 通过 thermal_zone_device+_ops的get_temp函数,读取底层温度// ret = tz->ops->get_temp(tz, temp);if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {for (count = 0; count < tz->num_trips; count++) {ret = __thermal_zone_get_trip(tz, count, &trip);if (!ret && trip.type == THERMAL_TRIP_CRITICAL) {crit_temp = trip.temperature;break;}}/** Only allow emulating a temperature when the real temperature* is below the critical temperature so that the emulation code* cannot hide critical conditions.*/if (!ret && *temp < crit_temp)*temp = tz->emul_temperature;}if (ret)dev_dbg(&tz->device, "Failed to get temperature: %d\n", ret);return ret;
}

在thermal zone注册时,就把这个函数传进来了,以就 rockchip_thermal 为例是这个:

static const struct thermal_zone_device_ops rockchip_of_thermal_ops = {.get_temp = rockchip_thermal_get_temp,.set_trips = rockchip_thermal_set_trips,
};static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
{struct rockchip_thermal_sensor *sensor = thermal_zone_device_priv(tz);struct rockchip_thermal_data *thermal = sensor->thermal;const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip;int retval;retval = tsadc->get_temp(&tsadc->table,sensor->id, thermal->regs, out_temp);return retval;
}
(3)rk_tsadcv4_get_temp

前文在【thermal sensor】中是以 rk3588_tsadc_data 为例的,此处还以此为例,则 “.get_temp = rk_tsadcv4_get_temp, // 从寄存器读出温度并转换成摄氏度”

static int rk_tsadcv4_get_temp(const struct chip_tsadc_table *table,int chn, void __iomem *regs, int *temp)
{u32 val;val = readl_relaxed(regs + TSADCV3_DATA(chn));// 对照 rk3588_code_table 查表,将寄存器值转换成温度return rk_tsadcv2_code_to_temp(table, val, temp); 
}

这样,内核可以拿到温升 sensor 的温度值。

3.3.2 thermal_zone_xxx

thermal_zone 相关的状态函数。

// thermal zone 开启
int thermal_zone_device_enable(struct thermal_zone_device *tz)
{return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_ENABLED);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_enable);// thermal zone 关闭
int thermal_zone_device_disable(struct thermal_zone_device *tz)
{return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_disable);// thermal zone 可用判断
int thermal_zone_device_is_enabled(struct thermal_zone_device *tz)
{lockdep_assert_held(&tz->lock);return tz->mode == THERMAL_DEVICE_ENABLED;
}// 对指定的 thermal zone 进行判断
static bool thermal_zone_is_present(struct thermal_zone_device *tz)
{return !list_empty(&tz->node);
}

thermal_zone_device_set_mode(),这是 Linux thermal 框架中用于设置 thermal zone 工作模式的函数。它的主要职责是:

  • 启用或禁用 thermal zone
  • 调用驱动回调进行模式切换
  • 更新当前 thermal zone 的温度状态
  • 向用户空间或其他内核模块发出通知
static int thermal_zone_device_set_mode(struct thermal_zone_device *tz,enum thermal_device_mode mode)
{int ret = 0;mutex_lock(&tz->lock);/* do nothing if mode isn't changing */// 如果新模式和当前 tz->mode 一致,则无需做任何事情if (mode == tz->mode) {mutex_unlock(&tz->lock);return ret;}// 检查 tz 对应的 device 是否已经注册到内核设备模型if (!device_is_registered(&tz->device)) {mutex_unlock(&tz->lock);return -ENODEV;}// 如果驱动提供了 change_mode 回调,就调用它if (tz->ops->change_mode)ret = tz->ops->change_mode(tz, mode);if (!ret)tz->mode = mode; // 如果 change_mode 执行成功(ret == 0),更新 tz->mode// 刷新当前 thermal zone 的状态// 如果启用 → 重新获取温度、处理 trip points。// 如果禁用 → 也会更新状态,可能停止 cooling device 调用。__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);mutex_unlock(&tz->lock);// 根据新模式,发送通知if (mode == THERMAL_DEVICE_ENABLED)thermal_notify_tz_enable(tz->id);elsethermal_notify_tz_disable(tz->id);return ret;
}

4 总结

4.1 thermal core 的作用

thermal core 是 Linux 内核热管理子系统的核心框架,位于 drivers/thermal/thermal_core.c 等文件中。它的主要职责是:

  • 统一框架
    提供热管理的基础框架,协调 热源(sensor/zone) 与 散热设备(cooling device)。
    将各类硬件温度传感器、风扇、CPU/GPU 降频、设备功耗管理等抽象统一起来。

  • 热区管理(Thermal Zone Management)
    每个 thermal zone 代表一个热源或逻辑区域(比如 CPU、GPU、battery)。
    通过驱动提供的回调函数获取温度、设置 trip point、定义温控策略。

  • 冷却设备管理(Cooling Device Management)
    定义通用 cooling device 框架,例如 CPUFreq、Devfreq、CPUIdle、风扇控制等。
    提供功率限制或性能限制的手段,来降低温度。

  • 策略调度(Governor)
    内置多种 governor(例如 step_wise、power_allocator、user_space)。
    根据当前温度和 trip point,决定如何调整 cooling device。

  • 用户空间接口
    提供 /sys/class/thermal/ sysfs 节点。
    用户空间可以读取温度、trip 信息,并可选择策略或注入用户空间 governor。
    支持 UEvent 通知用户空间(如 Android framework thermal service)。

4.2 热管理流程(工作机制)

thermal core 的热管理大体可以分为以下几个步骤:

(1) 注册阶段

  • 设备驱动注册 thermal zone(thermal_zone_device_register)。
  • 设备驱动注册 cooling device(thermal_cooling_device_register)。
  • thermal core 将 zone 与 device 绑定(通过 bind() 回调或 device tree 绑定)。

(2) 监测阶段

  • thermal core 定期轮询 thermal zone(或由传感器中断触发)。
  • 调用 zone ops → get_temp() 获取当前温度。
  • 比较温度和 trip point(如 passive, active, critical)。

(3) 决策阶段

  • 如果温度超过阈值 → governor 介入:
    step_wise governor: 按步进方式提高 cooling device 的等级。
    power_allocator governor: 根据 PID 算法分配 cooling device 功率。
    user_space governor: 把信息抛给用户空间,让策略由上层决定。

(4) 执行阶段

  • governor 调用 thermal_cooling_device->ops->set_cur_state() 等接口。
    比如:降低 CPU 频率(cpufreq)、限制 GPU 带宽(devfreq)、开启风扇。

(5) 用户空间交互

  • 通过 sysfs 查看和调节参数:
    /sys/class/thermal/thermal_zone*/temp,/sys/class/thermal/cooling_device*/cur_state
  • 通过 kobject_uevent_env() 通知用户空间 thermal 事件(Android 就是监听这些事件)。

4.3 时序参考

在这里插入图片描述

http://www.dtcms.com/a/609901.html

相关文章:

  • 网站的维护费用售后服务网站建设
  • Databend SQL nom Parser 性能优化
  • wordpress的标签页网站seo竞争分析工具
  • Clip模型与Vit模型的区别?
  • 前端 CSS selector
  • 《嵌入式开发硬核指南:91问一次讲透底层到架构》
  • 贵阳市网站建设wordpress改为邮箱验证注册
  • 深入解析与应用:Delphi-2M 健康轨迹预测模型的开源实践与研究(下)
  • 可信网站值得做吗网站中怎么做下载链接
  • 在 UniApp 中为小程序实现视频播放记录功能
  • 嗑一下Vue3 生态新插件
  • 31、【Ubuntu】【远程开发】内网穿透:反向隧道建立(三)
  • ubuntu20.04下使用D435i实时运行ORB-SLAM3
  • 网站建设哪便宜wordpress建手机版6
  • 东莞如何搭建网站建设wordpress视频压缩
  • Rust 宏:深入理解与高效使用
  • 基于异质专家协同一致性学习的弱监督红外 - 可见光行人重识别
  • 挂载配置文件以Docker启动Redis服务
  • 网站被墙怎么做跳转深圳龙岗个人网站建设
  • 标准输入输出stdio和JSON-RPC
  • 免费seo网站推荐一下软件手机网站建立教程
  • 有哪些网站可以用常州小程序开发报价
  • Python自动化浏览器操作与定时任务实战指南
  • web中国民族文化展示网站4页面
  • 【剑斩OFFER】算法的暴力美学——【模板】前缀和
  • php网站建设考试新品发布会的作用
  • 视频模板网站推荐建筑装饰网站模板
  • PyCharm 软件关联 GitHub 账户
  • 中东核心支付方式
  • 2025数维杯秋季赛赛中陪跑助攻进行中