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 时序参考

相关推荐
阿巴斯甜17 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴20 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android