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:只在内核启动阶段使用,函数和常量可在启动完成后被丢弃(节约内存)。

c 复制代码
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 时调整策略)。温升/功耗基本是不分家的,因此在控温的情况,是否需要严格控制功耗,需要电源管理系统支持。

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

c 复制代码
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 验证并安全抽取数据.

c 复制代码
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 调用来响应请求。

c 复制代码
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() 发送事件。

c 复制代码
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()

c 复制代码
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:没用到的额外参数。
c 复制代码
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 zone
		list_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 zone
			thermal_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的注册流程如下:

c 复制代码
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:轮询延迟(被动冷却 / 温度检测)
c 复制代码
/**
 * 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);
	}

	// 校验trips
	if (num_trips > 0 && !trips)
		return ERR_PTR(-EINVAL);

	// 校验全局 thermal_class 是否初始化
	// thermal 子系统还没初始化时,不能注册 zone
	if (!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 zone
		tz->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_instances
	INIT_LIST_HEAD(&tz->node); // struct list_head node
	ida_init(&tz->ida); // struct ida ida
	mutex_init(&tz->lock); // struct mutex lock
	init_completion(&tz->removal); // struct completion removal
	// 分配一个全局 thermal zone id,例如 thermal_zone0
	id = 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%d
	result = 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 或系统默认 governor
	if (tz->tzp)
		governor = __find_governor(tz->tzp->governor_name); // __find_governor 根据名称找到 governor 对象
	else
		governor = def_governor;

	// 设置控制策略,之后 thermal zone 可以直接调用 governor
	result = 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_list
	list_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_check
	INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);

	/* Update the new thermal zone and mark it as already updated. */
	// 第一次强制更新温度状态
	// 设置 thermal zone need_update=1
	if (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 里。

c 复制代码
#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 目录下。

c 复制代码
// 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 结尾,需要 +1
	size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2;
	/* This also takes care of API requirement to be NULL terminated */
	// 分配一个数组,每个元素指向 struct attribute_group
	groups = kcalloc(size, sizeof(*groups), GFP_KERNEL);
	if (!groups)
		return -ENOMEM;

	// 将标准属性组(如 temp, mode, policy 等)复制到 groups 前面部分。
	// 留下最后两个槽位:一个给 trips,一个 NUL
	for (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 函数。

c 复制代码
// 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 */
	// 检查是否有 trip
	if (tz->num_trips <= 0)
		return -EINVAL;

	// 分配三类属性数组
	// step 1: 分配数组 type
	tz->trip_type_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_type_attrs),
				      GFP_KERNEL);
	if (!tz->trip_type_attrs)
		return -ENOMEM;
    
    // step 2: 分配数组 temp
	tz->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: 分配数组 hyst
	tz->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_type
		tz->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 → 变成可写 0644
			tz->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 事件。

c 复制代码
//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 的操作都是基于这个指针对象来进行的。去掉不必要的信息,简化如下:

c 复制代码
// 用个指针指向这样一个结构体对象,装载众多信息
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
c 复制代码
 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。
c 复制代码
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
c 复制代码
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 注册来看,如下:

c 复制代码
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 调度使用。

c 复制代码
/**
 * 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)。
c 复制代码
/**
 * __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_list
	list_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 准备属性文件 的关键逻辑。

c 复制代码
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/ 下创建这些文件。

核心的函数实现,如下:

c 复制代码
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 device
	stats->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 。

c 复制代码
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 注册过程:

c 复制代码
thermal_init // thermal core 初始化函数
↓
thermal_register_governors
↓
thermal_register_governor

2.3.1 __init thermal_register_governors

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

c 复制代码
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 这个宏定义如下:

c 复制代码
// 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。

c 复制代码
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作为默认的governor
		if (!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 设置为当前注册进来的 governor
			ret = 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 的配置名字做匹配,只有名字一致的才会被绑定。

【举例】

c 复制代码
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 等)。
c 复制代码
/**
 * 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_unbind
		tz->governor->unbind_from_tz(tz);

	// 新 governor 的绑定
	if (new_gov && new_gov->bind_to_tz) {
		// 以 power_allocator 为例就是 power_allocator_bind
		ret = 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 已经切换到新的 governor
	tz->governor = new_gov;
	return ret;
}

继续看下 bind_previous_governor:

c 复制代码
/**
 * 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_bind
		if (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 上绑定起来,并且携带这个绑定关系的上下文信息。

c 复制代码
/*
 * 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,在内核中分配,主要用于区分不同 instance
	char 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 state
	unsigned long upper;	/* Highest cooling state for this trip point */
	// lower = 最小可用的 cooling state
	unsigned long lower;	/* Lowest cooling state for this trip point */
	// governor 计算出的 目标 cooling state
	unsigned 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

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

c 复制代码
// 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

c 复制代码
// 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 表示这是一个空链表。

关键定义:

c 复制代码
// drivers/thermal/thermal_core.c

thermal_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 之后(链表头部插入)。

c 复制代码
/*
 * 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

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

c 复制代码
/**
 * 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

c 复制代码
/*
 * 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,都是全局定义

c 复制代码
// drivers/thermal/thermal_core.c

static 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。

c 复制代码
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。

c 复制代码
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)关系说明

简化一下几个模块的结构体定义,如下:

c 复制代码
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_governor
	struct 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 函数。执行如下:

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

3.1.2 thermal_zone_device_update

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

c 复制代码
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 配合真延迟时间,进行轮询回调。

c 复制代码
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);
	else
		cancel_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 索引。

c 复制代码
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() 通常是立即关机、重启、进入紧急保护状态(避免硬件损坏)。

c 复制代码
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)。继续,

c 复制代码
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 温控策略支持。

c 复制代码
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 的调用链了,之前有所分析,如下图:

简单看下这条调用逻辑:

c 复制代码
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)
c 复制代码
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
c 复制代码
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 为例是这个:

c 复制代码
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, // 从寄存器读出温度并转换成摄氏度"

c 复制代码
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 相关的状态函数。

c 复制代码
// 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 的温度状态
  • 向用户空间或其他内核模块发出通知
c 复制代码
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);
	else
		thermal_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 时序参考

相关推荐
一氧化二氢.h2 小时前
MySQL root用户连接错误解决方法
android·数据库·mysql
QuantumLeap丶2 小时前
《Flutter全栈开发实战指南:从零到高级》- 13 -状态管理GetX
android·flutter·ios·前端框架
百锦再3 小时前
第15章 并发编程
android·java·开发语言·python·rust·django·go
Propeller3 小时前
【Android】模板化解决复杂场景的滑动冲突问题
android·java
byte轻骑兵4 小时前
Rust赋能Android蓝牙协议栈:从C++到安全高效的重构之路
android·c++·rust
從南走到北6 小时前
JAVA国际版二手车交易二手车市场系统源码支持Android+IOS+H5+APP
android·java·ios
江上清风山间明月6 小时前
Android 系统中进程和线程的区别
android·python·线程·进程
2501_940094027 小时前
mig烧录卡资源 Mig-Switch游戏合集 烧录卡 1.75T
android·游戏·安卓·switch
渡我白衣7 小时前
深入理解 OverlayFS:用分层的方式重新组织 Linux 文件系统
android·java·linux·运维·服务器·开发语言·人工智能