目录
一、Thermal 框架
Linux 中的 thermal 子系统用于监控和管理设备的温度,以防止设备过热导致硬件损坏。
该子系统通过读取传感器数据来获取温度信息,并根据预设的策略采取措施来调节温度。
如图所示,linux thermal框架中至少包含以下几部分:
- Thermal Core:Thermal Core 作为 Thermal 框架的中枢,提供了一系列注册 Governor、注册 Thermal 类、基于Device Tree注册Thermal Zone 的一系列接口;
- Thermal Driver/Thermal Sensor:为整个 Thermal 提供读取温度、设置 trip 温度等功能的接口;
- Thermal Governor:Thermal Governor 决定了当温度达到某个阈值时,系统应该采取什么措施,常见的 Governor 有:Power Allocator、Step Wise、User Space、FairShart等;
- Thermal Cooling:Thermal Cooling作为温控系统的输出,包含了用于降温的实际硬件或软件方法。在 pc 上常见的 cooling设备有风扇,而在移动设备上主要通过降频降压实现降功耗,这些设备可以是 CPU(cpufreq子系统)、devfreq(devfreq子系统)、clock(clock子系统)等;
二、关键结构体
2.1 thermal zone device
2.1.1 thermal_zone_device
thermal_zone_device
表示一个thermal zone设备:
struct thermal_zone_device {
int id;
char type[THERMAL_NAME_LENGTH]; // thermal zone name
struct device device;
struct attribute_group trips_attribute_group; // 与触发点相关的属性组
struct thermal_attr *trip_temp_attrs; // 触发点温度属性的数组
struct thermal_attr *trip_type_attrs; // 触发点类型属性的数组
struct thermal_attr *trip_hyst_attrs; // 触发点迟滞值属性的数组
void *devdata;
int trips; // 触发点数量
unsigned long trips_disabled; /* bitmap for disabled trips */
int passive_delay; // 被动轮询的延迟时间(ms)
int polling_delay; // 主动轮询的延迟时间(ms)
int temperature; // 当前温度
int last_temperature; // 上一次读取到的温度
int emul_temperature; // 模拟温度,用于测试和调试
int passive; // 被动冷却状态的标志
int prev_low_trip; // 温度低于该值时,表示温度降低到另一个trip区域
int prev_high_trip; // 温度高于该值时,表示温度升高到另一个trip区域
unsigned int forced_passive; // 强制冷却的标记,当温度发生区间跨越时,会被置位
atomic_t need_update; // 强制update标记,当注册thermal zone和添加新的cooling设备时需要置位
struct thermal_zone_device_ops *ops; // 包含thermal zone操作接口
struct thermal_zone_params *tzp; // 包含thermal zone参数
struct thermal_governor *governor; // thermal zone当前的governor
void *governor_data; // governor的私有数据
struct list_head thermal_instances;
struct ida ida;
struct mutex lock;
struct list_head node;
struct delayed_work poll_queue;
enum thermal_notify_event notify_event;
};
2.1.2 thermal_zone_device_ops
thermal_zone_device_ops
结构体包含了thermal_zone_device的一系列操作接口:
struct thermal_zone_device_ops {
int (*bind) (struct thermal_zone_device *,
struct thermal_cooling_device *); // 绑定 cooling device 到 thermal_zone_device
int (*unbind) (struct thermal_zone_device *,
struct thermal_cooling_device *); // 解绑
int (*get_temp) (struct thermal_zone_device *, int *); // 获取温度
int (*set_trips) (struct thermal_zone_device *, int, int); // 设置触发点温度
int (*get_mode) (struct thermal_zone_device *,
enum thermal_device_mode *); // 获取thermal zone的模式,enable/disable
int (*set_mode) (struct thermal_zone_device *,
enum thermal_device_mode); // 设置thermal zone的模式
int (*get_trip_type) (struct thermal_zone_device *, int,
enum thermal_trip_type *); // 获取trip的告警类型
int (*get_trip_temp) (struct thermal_zone_device *, int, int *); // 获取trip的触发点温度
int (*set_trip_temp) (struct thermal_zone_device *, int, int);
int (*get_trip_hyst) (struct thermal_zone_device *, int, int *); // 获取trip的迟滞温度
int (*set_trip_hyst) (struct thermal_zone_device *, int, int);
int (*get_crit_temp) (struct thermal_zone_device *, int *); // 获取thermal zone的critical 温度阈值
int (*set_emul_temp) (struct thermal_zone_device *, int); // 设置模拟温度,模拟测试调试使用
int (*get_trend) (struct thermal_zone_device *, int,
enum thermal_trend *); // 获取温度的趋势,上升、下降、稳定
int (*notify) (struct thermal_zone_device *, int,
enum thermal_trip_type); // 当温度达到critical温度是,调用的自定义的notify接口
};
2.2 thermal governor
thermal governor描述了温控系统中的调温策略,thermal_governor
结构体表示了一个thermal governor:
struct thermal_governor {
char name[THERMAL_NAME_LENGTH]; // governor name
int (*bind_to_tz)(struct thermal_zone_device *tz); // governor绑定到thermal zone
void (*unbind_from_tz)(struct thermal_zone_device *tz); // 解绑
int (*throttle)(struct thermal_zone_device *tz, int trip); // 温度到达某触发点时的回调接口
struct list_head governor_list;
};
2.3 cooling device
2.3.1 thermal_cooling_device
thermal_cooling_device
表示thermal框架中的一个冷却设备:
struct thermal_cooling_device {
int id;
char type[THERMAL_NAME_LENGTH]; // 名称
struct device device;
struct device_node *np; // 设备树中的设备节点
void *devdata; // cooling device的私有数据
void *stats; // 统计设备状态的统计数据
const struct thermal_cooling_device_ops *ops; // cooling device的操作接口
bool updated; /* true if the cooling device does not need update */
struct mutex lock; /* protect thermal_instances list */
struct list_head thermal_instances;
struct list_head node;
};
2.3.2 thermal_cooling_device_ops
thermal_cooling_device_ops
包含了cooling device的操作接口:
struct thermal_cooling_device_ops {
int (*get_max_state) (struct thermal_cooling_device *, unsigned long *); // 返回设备的最大状态,表示设备的最高冷却能力
int (*get_cur_state) (struct thermal_cooling_device *, unsigned long *); // 当前状态
int (*set_cur_state) (struct thermal_cooling_device *, unsigned long); // 设置冷却设备的状态,用于调整冷却状态;
int (*get_requested_power)(struct thermal_cooling_device *,
struct thermal_zone_device *, u32 *); // 返回设备当前power
int (*state2power)(struct thermal_cooling_device *,
struct thermal_zone_device *, unsigned long, u32 *); // cooling device state 转换为power
int (*power2state)(struct thermal_cooling_device *,
struct thermal_zone_device *, u32, unsigned long *); // cooling device power转换为state
};
2.4 thermal trips
thermal_trip
结构体用于表示一个thermal zone中的一个温度区间,包含区间的触发温度、迟滞温度、区间类型等信息:
struct thermal_trip {
struct device_node *np;
int temperature; // 温度触发阈值
int hysteresis; // 基于温度触发阈值的迟滞值
enum thermal_trip_type type; // 该触发节点的告警等级
};
thermal_trip_type
是指触发节点的告警等级,分为以下四个等级:
enum thermal_trip_type {
THERMAL_TRIP_ACTIVE = 0, // 一般指正常状态
THERMAL_TRIP_PASSIVE, // 一般需要开始温度控制了
THERMAL_TRIP_HOT, // 需要执行更近一步的特殊控温动作,并通知必要应用
THERMAL_TRIP_CRITICAL, // 严重等级,必要的话需要关机了
};
三、Thermal Core
3.1 thermal 初始化
thermal_init 是 linux thermal 子系统的初始化接口:
static int __init thermal_init(void)
// 初始化 poweroff_lock,用于critical_trip重启系统时,防止重复调用power off接口;
-> mutex_init(&poweroff_lock);
// 遍历 governor 列表,注册governor
-> thermal_register_governors();
-> for_each_governor_table(governor) { thermal_register_governor(*governor); }
-> list_add(&governor->governor_list, &thermal_governor_list);
// 如果 governor 是 thermal zone的期望gov,
// 则将governor设置为 thermal zone 的governor;
-> thermal_set_governor(pos, governor);
// 注册 thermal class,用于在 /sys/class/thermal 中创建 thermal 管理相关的文件系统;
-> class_register(&thermal_class);
// 初始化通用网络链接,如果 CONFIG_NET=y,则可以通过网络进行温度管理通信;
-> genetlink_init();
// 解析设备树中的 thermal zone(并注册thermal zone设备);
-> of_parse_thermal_zones();
// 注册电源管理通知回调,用于在系统睡眠和唤醒时接受通知的调用;
-> register_pm_notifier(&thermal_pm_nb);
}
fs_initcall(thermal_init);
3.2 设备树解析
3.2.1 of_parse_thermal_zones
linux thermal 框架中,使用 of_parse_thermal_zones
接口解析设备树中的thermal zone:
int __init of_parse_thermal_zones(void)
{
// 查找设备树中名为"thermal-zones" 的节点
np = of_find_node_by_name(NULL, "thermal-zones");
// 遍历np的所以子节点,每一个子节点表示一个thermal zone
for_each_available_child_of_node(np, child) {
// 从子节点解析thermal zone的配置,并根据配置创建 __thermal_zone,
tz = thermal_of_build_thermal_zone(child);
// 分配 thermal_zone_device_ops 和 thermal_zone_params的内存
ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);
tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);
// thermal_zone_params 部分参数的配置
tzp->no_hwmon = true;
if (!of_property_read_u32(child, "sustainable-power", &prop))
tzp->sustainable_power = prop;
for (i = 0; i < tz->ntrips; i++)
mask |= 1 << i;
tzp->slope = tz->slope;
tzp->offset = tz->offset;
// 注册 thermal_zone_device
zone = thermal_zone_device_register(child->name, tz->ntrips,
mask, tz,
ops, tzp,
tz->passive_delay,
tz->polling_delay);
}
of_node_put(np);
return 0;
}
3.2.2 thermal_of_build_thermal_zone
thermal_of_build_thermal_zone
接口用于从thermal zone设备树节点解析thermal zone的配置:
// 从子节点解析thermal zone的配置,并根据配置创建 __thermal_zone,
tz = thermal_of_build_thermal_zone(child);
// 读取轮询延时属性
-> ret = of_property_read_u32(np, "polling-delay-passive", &prop);
-> tz->passive_delay = prop;
-> ret = of_property_read_u32(np, "polling-delay", &prop);
-> tz->polling_delay = prop;
// 读取系数,表示每个温度传感器对thermal zone影响的系数关系
-> ret = of_property_read_u32_array(np, "coefficients", coef, 2);
// 解析trips设备树子节点,trips子节点中设置了温度触发节点等信息
-> child = of_get_child_by_name(np, "trips");
-> tz->ntrips = of_get_child_count(child); // 个数
-> tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL); // 申请内存
-> for_each_child_of_node(child, gchild)
-> ret = thermal_of_populate_trip(gchild, &tz->trips[i++]); // 解析节点
-> of_node_put(child);
// 解析cooling-maps子节点,该节点中包含了每个温度区间对应的冷却设备和冷却等级
-> child = of_get_child_by_name(np, "cooling-maps");
-> tz->num_tbps = of_get_child_count(child);
-> tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL);
-> for_each_child_of_node(child, gchild)
-> ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++],
tz->trips, tz->ntrips); // 解析节点
-> of_node_put(child);
3.3 thermal zone device 注册
thermal_zone_device_register
接口用于注册一个thermal zone device:
thermal_zone_device_register(const char *type, int trips, int mask,
void *devdata, struct thermal_zone_device_ops *ops,
struct thermal_zone_params *tzp, int passive_delay,
int polling_delay)
- type: thermal zone 的类型名称。
- trips: thermal zone 的trips数目。
- mask: thermal zone设备的掩码,用于表示启用或禁用的trips。
- devdata: 指向设备私有数据的指针。
- ops: 指向 thermal zone device 操作函数的指针,包括获取温度、设置阈值等操作。
- tzp: thermal zone 的参数。
- passive_delay: 被动模式下的轮询延迟时间(毫秒)。
- polling_delay: 主动模式下的轮询延迟时间(毫秒)。
struct thermal_zone_device *
thermal_zone_device_register(const char *type, int trips, int mask,
void *devdata, struct thermal_zone_device_ops *ops,
struct thermal_zone_params *tzp, int passive_delay,
int polling_delay)
{
struct thermal_zone_device *tz;
// 申请thermal_zone_device内存,并根据接口入参,初始化tz
tz = kzalloc(sizeof(*tz), GFP_KERNEL);
INIT_LIST_HEAD(&tz->thermal_instances);
ida_init(&tz->ida);
mutex_init(&tz->lock);
id = ida_simple_get(&thermal_tz_ida, 0, 0, GFP_KERNEL);
tz->id = id;
strlcpy(tz->type, type, sizeof(tz->type));
tz->ops = ops;
tz->tzp = tzp;
tz->device.class = &thermal_class;
tz->devdata = devdata;
tz->trips = trips;
tz->passive_delay = passive_delay;
tz->polling_delay = polling_delay;
// 创建thermal zone device的属性节点
result = thermal_zone_create_device_groups(tz, mask);
// 新的thermal zone device, 将need_update置1,表示设备需要更新温度
atomic_set(&tz->need_update, 1);
dev_set_name(&tz->device, "thermal_zone%d", tz->id);
// 检查每个trip的有效性,如果接口测试失败,则将trip设置为disable
for (count = 0; count < trips; count++) {
if (tz->ops->get_trip_type(tz, count, &trip_type))
set_bit(count, &tz->trips_disabled);
if (tz->ops->get_trip_temp(tz, count, &trip_temp))
set_bit(count, &tz->trips_disabled);
/* Check for bogus trip points */
if (trip_temp == 0)
set_bit(count, &tz->trips_disabled);
}
/* Update 'this' zone's governor information */
mutex_lock(&thermal_governor_lock);
// 设置tz的governor
result = thermal_set_governor(tz, governor);
mutex_unlock(&thermal_governor_lock);
// 添加监控sysfs
if (!tz->tzp || !tz->tzp->no_hwmon) {
result = thermal_add_hwmon_sysfs(tz);
if (result)
goto unregister;
}
mutex_lock(&thermal_list_lock);
list_add_tail(&tz->node, &thermal_tz_list);
mutex_unlock(&thermal_list_lock);
/* Bind cooling devices for this zone */
bind_tz(tz);
// 初始化温度监控的延时任务
INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
thermal_zone_device_reset(tz);
/* Update the new thermal zone and mark it as already updated. */
if (atomic_cmpxchg(&tz->need_update, 1, 0))
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
return tz;
}
EXPORT_SYMBOL_GPL(thermal_zone_device_register);
3.4 thermal 温度监控
如图所示是thermal框架中温度监控的流程:
- 在循环监控任务中,thermal core 从 thermal sensor 读取温度;
- 根据温度和 thermal zone 中的各温度区间的阈值和属性,确定当前 type 类型;
- 非 hot 和 critical 类型则由 thermal governor 执行控温策略,根据当前温度调用必要的 cooling 手段;
- hot 类型需要通知必要 app;
- critical 类型可能需要关机了;
实际是通过 delayed_work实现这个循环监控任务的。
3.4.1 thermal_zone_device_check
在thermal zone device 注册的时候,注册了一个延时任务:
INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
thermal_zone_device_check
实际就是调用了thermal_zone_device_update
接口:
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);
}
3.4.2 thermal_zone_device_update
thermal_zone_device_update
接口才是实际的温度监控程序中的核心流程:
void thermal_zone_device_update(struct thermal_zone_device *tz,
enum thermal_notify_event event)
{
// 获取温度
update_temperature(tz);
// 更新thermal zone的最新告警温度(如果设备支持温度触发中断可需要此接口,大部分实现不需要)
thermal_zone_set_trips(tz);
tz->notify_event = event;
// 针对每个温度告警区间,执行handle_thermal_trip
for (count = 0; count < tz->trips; count++)
handle_thermal_trip(tz, count);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_update);
handle_thermal_trip
接口中,会检查当前温度是否触发告警条件,检查完毕后,调用monitor_thermal_zone
接口重新设置延时任务thermal_zone_device_check
的下次调用时间:
handle_thermal_trip(tz, count);
-> if (test_bit(trip, &tz->trips_disabled)) return; // 温控区间已禁用的话直接返回
-> tz->ops->get_trip_type(tz, trip, &type); // 获取温控区间的类型
-> handle_critical_trips(tz, trip, type); // critical或者hot trip类型
-> tz->ops->notify(tz, trip, trip_type); // 超出critical或hot告警阈值的话,执行notify回调
-> thermal_emergency_poweroff(); / orderly_poweroff(true); // critical 类型触发重启
-> handle_non_critical_trips(tz, trip); // 其它trip类型
// 其它trip类型,执行governor的调温策略
-> tz->governor ? tz->governor->throttle(tz, trip) : def_governor->throttle(tz, trip);
-> monitor_thermal_zone(tz);
3.4.3 延时任务的触发机制
- 第一次触发:在thermal zone device注册和cooling device注册的时候,都会调用
thermal_zone_device_update
更新温度检查;. - 后续触发:在
handle_thermal_trip
接口中,会调用monitor_thermal_zone
接口,该接口根据thermal zone当前的状态,设置延时任务thermal_zone_device_check
的下次调用时间:
// 设置delay work的下次触发时间
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
int delay)
{
if (delay > 1000)
mod_delayed_work(system_freezable_power_efficient_wq,
&tz->poll_queue,
round_jiffies(msecs_to_jiffies(delay)));
else if (delay)
mod_delayed_work(system_freezable_power_efficient_wq,
&tz->poll_queue,
msecs_to_jiffies(delay));
else
cancel_delayed_work(&tz->poll_queue);
}
// 根据thermal zone的状态
// passive状态,设置下次延时任务调用时间为passive_delay
// 常规状态,设置下次延时任务调用时间为polling_delay
static void monitor_thermal_zone(struct thermal_zone_device *tz)
{
mutex_lock(&tz->lock);
if (tz->passive)
thermal_zone_device_set_polling(tz, tz->passive_delay);
else if (tz->polling_delay)
thermal_zone_device_set_polling(tz, tz->polling_delay);
else
thermal_zone_device_set_polling(tz, 0);
mutex_unlock(&tz->lock);
}
四、Thermal Governor
在thermal子系统中,governor 是掌握调控温度策略的关键组件。它通过调节系统中各种控温设备(如 CPU、GPU、风扇等),确保系统在安全的温度范围内运行。
关于 governor 的结构体已经在上面 2.2 thermal governor 中做了说明,下面讲述常用的几种 governor 和注册和使用方式。
4.1 声明和注册
4.1.1 governor 的声明
以 step_wise governor 为例,使用 THERMAL_GOVERNOR_DECLARE
宏将 thermal_gov_step_wise 结构体添加特定的段:__governor_thermal_table
表中。
static struct thermal_governor thermal_gov_step_wise = {
.name = "step_wise",
.throttle = step_wise_throttle,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise);
thermal_governor 中的 throttle 是thermal core每次读取温度后回调接口
THERMAL_GOVERNOR_DECLARE
宏简化了thermal governor 声明的过程,以 step_wise 为例,将这个宏展开为:
// 宏定义:
/* Init section thermal table */
extern struct thermal_governor *__governor_thermal_table[];
extern struct thermal_governor *__governor_thermal_table_end[];
#define THERMAL_TABLE_ENTRY(table, name) \
static typeof(name) *__thermal_table_entry_##name \
__used __section(__##table##_thermal_table) = &name
#define THERMAL_GOVERNOR_DECLARE(name) THERMAL_TABLE_ENTRY(governor, name)
// 展开:
THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise);
==> THERMAL_TABLE_ENTRY(governor, thermal_gov_step_wise);
==> static typeof(thermal_gov_step_wise) *__thermal_table_entry_thermal_gov_step_wise \
__used __section(__governor_thermal_table) = &thermal_gov_step_wise;
展开后,__thermal_table_entry_thermal_gov_step_wise
是指向 thermal_gov_step_wise
的一个指针,并被放在 __governor_thermal_table
段中。
governor 的声明,主要就是使用 THERMAL_GOVERNOR_DECLARE
宏将 thermal_governor 结构体放在全局表 __governor_thermal_table
中,方便系统初始化的时候自动遍历和注册这些 governor,也方便添加新的 governor。
4.1.2 governor 的注册
在 thermal_init
时,会调用 thermal_register_governors
接口,该接口中遍历 __governor_thermal_table
,对表中每个 governor 执行 thermal_register_governor
实现 governor 的注册。
static int __init thermal_register_governors(void)
{
int ret = 0;
struct thermal_governor **governor;
for_each_governor_table(governor) {
ret = thermal_register_governor(*governor);
}
return ret;
}
thermal_register_governor
接口负责注册一个 governor,并将其与合适的 thermal zone 关联。
int thermal_register_governor(struct thermal_governor *governor)
{
// 如果在thermal_governor_list没有该governor,
// 则将该governor加到thermal_governor_list链表中
// 并判断该governor是否是default governor
if (!__find_governor(governor->name)) {
bool match_default;
list_add(&governor->governor_list, &thermal_governor_list);
match_default = !strncmp(governor->name,
DEFAULT_THERMAL_GOVERNOR,
THERMAL_NAME_LENGTH);
if (!def_governor && match_default)
def_governor = governor;
}
// 遍历thermal zone列表,如果thermal zone指定了governor
// 判断当前governor是否是 thermal zone知道的governor
// 是的话则thermal_set_governor,设置thermal zone的governor为当前governor
list_for_each_entry(pos, &thermal_tz_list, node) {
if (pos->governor)
continue;
name = pos->tzp->governor_name;
if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
thermal_set_governor(pos, governor);
}
}
return err;
}
4.2 几种常用的governor
4.2.1 user_space governor
user_space governor 通过 uevent 将当前温度、温控触发点等信息上报到用户空间,由用户空间软件制定温控的策略。
static int notify_user_space(struct thermal_zone_device *tz, int trip)
{
char *thermal_prop[5];
int i;
mutex_lock(&tz->lock);
thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type);
thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature);
thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip);
thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event);
thermal_prop[4] = NULL;
kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop);
for (i = 0; i < 4; ++i)
kfree(thermal_prop[i]);
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor thermal_gov_user_space = {
.name = "user_space",
.throttle = notify_user_space,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_user_space);
4.2.2 step_wise governor
step_wise governor 中添加了 trend 表示温度变化的趋势,并根据温度变化的趋势、结合当前的温度,综合决定 cooling 策略。
thermal_trend 包含以下几种状态:
enum thermal_trend {
THERMAL_TREND_STABLE, /* temperature is stable */
THERMAL_TREND_RAISING, /* temperature is raising */
THERMAL_TREND_DROPPING, /* temperature is dropping */
THERMAL_TREND_RAISE_FULL, /* apply highest cooling action */
THERMAL_TREND_DROP_FULL, /* apply lowest cooling action */
};
step_wise 计算 cooling device state的流程:
4.2.3 bang_bang governor
bang_bang governor 是一种实现了简单的开\关策略的 governor:
- 当温度超过阈值时,则打开控温设备,当温度低于阈值时,则关闭控温设备;
- 只有开\关两种状态,没有其它中间状态;
4.2.4 power_allocator governor
power_allocator governor 使用较为复杂的算法,通过动态分配功率以管理系统温度。
这部分比较复杂,没有深入研究,感兴趣的可以参考Android/Linux Thermal Governor之IPA分析与使用
五、Thermal sensor
在linux thermal 子系统中,每个 thermal zone 都是和一个温度传感器绑定的,通过该thermal sensor提供的温度,决定整个 thermal zone 温控动作。
5.1 thermal sensor 注册
一个 thermal sensor 影响范围是一个 thermal zone, thermal sensor 的注册实际就是将sensor 与 thermal zone 绑定。
可以通过 devm_thermal_zone_of_sensor_register
接口方便的注册 thermal sensor:
- 该接口返回值是注册的
thermal_zone_device
thermal zone 设备; - 该接口内会搜索 dts 中的 “thermal-zones”, 并解析该节点完成设备注册;
struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
struct device *dev, int sensor_id,
void *data, const struct thermal_zone_of_device_ops *ops)
- dev:指向注册thermal zone设备的设备结构体的指针。
- sensor_id:传感器的 ID。
- data:传递给回调函数的私有数据。
- ops:指向thermal zone操作结构体的指针,该结构体定义了与传感器交互的操作。
struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
struct device *dev, int sensor_id,
void *data, const struct thermal_zone_of_device_ops *ops)
{
struct thermal_zone_device **ptr, *tzd;
// 分配设备管理资源
ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr),
GFP_KERNEL);
// 注册 thermal zone
tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops);
*ptr = tzd;
devres_add(dev, ptr);
return tzd;
}
devm_thermal_zone_of_sensor_register
接口主要分配设备管理资源,thermal sensor 的注册主要在thermal_zone_of_sensor_register
接口中完成:
struct thermal_zone_device *
thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data,
const struct thermal_zone_of_device_ops *ops)
{
struct device_node *np, *child, *sensor_np;
struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
// 查找 "thermal-zones" dts节点
np = of_find_node_by_name(NULL, "thermal-zones");
// 遍历每个thermal-zone,查找使用sensor_np 的thermal-zone
// sensor_np是要注册的thermal sensor
sensor_np = of_node_get(dev->of_node);
for_each_available_child_of_node(np, child) {
struct of_phandle_args sensor_specs;
int ret, id;
// 解析thermal-zone中设置的thermal-sensors
ret = of_parse_phandle_with_args(child, "thermal-sensors",
"#thermal-sensor-cells",
0, &sensor_specs);
// 检查thermal-zone中设置的thermal-sensor是否是 sensor_np
// 是的话,thermal_zone_of_add_sensor
if (sensor_specs.np == sensor_np && id == sensor_id) {
tzd = thermal_zone_of_add_sensor(child, sensor_np,
data, ops);
}
}
return tzd;
}
EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register);
thermal_zone_of_sensor_register
接口查找dts 中的 “thermal-zones” 节点,该节点的每个子节点都是一个thermal zone,遍历每个子节点,对使用当前要注册的sensor的子节点,将 sensor 与 thermal zone 绑定:
static struct thermal_zone_device *
thermal_zone_of_add_sensor(struct device_node *zone,
struct device_node *sensor, void *data,
const struct thermal_zone_of_device_ops *ops)
{
struct thermal_zone_device *tzd;
struct __thermal_zone *tz;
tzd = thermal_zone_get_zone_by_name(zone->name);
tz = tzd->devdata;
tz->ops = ops;
tz->sensor_data = data;
tzd->ops->get_temp = of_thermal_get_temp;
tzd->ops->get_trend = of_thermal_get_trend;
if (ops->set_trips)
tzd->ops->set_trips = of_thermal_set_trips;
if (ops->set_emul_temp)
tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
return tzd;
}
thermal_zone_of_add_sensor
接口主要是将传入参数的 sensor ops 操作函数、私有数据 data 绑定到 thermal_zone_device 实现 sensor 与 thermal zone 的绑定。
六、Thermal cooling device
linux thermal 子系统中的 cooling device 包含风扇、电源、cpu、可以调频调压的设备、时钟等,涉及到 linux 中的 acpi、cpufreq、devfreq、clock等子系统。
注册上述设备时,如果设备的 dts 节点中配置了 “#cooling-cells” 字段,并调用接口将设备注册为thermal cooling device 的话,则thermal子系统能通过对应的接口对cooling device进行调节。
6.1 cooling device 注册
使用 __thermal_cooling_device_register
接口注册 cooling device,
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;
struct thermal_zone_device *pos = NULL;
int result;
// 申请结构体内存、分配cooling device设备id
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
result = ida_simple_get(&thermal_cdev_ida, 0, 0, GFP_KERNEL);
cdev->id = result;
// 初始化cooling device结构体的各个字段,包括类型、锁、操作函数ops等
strlcpy(cdev->type, type ? : "", sizeof(cdev->type));
mutex_init(&cdev->lock);
INIT_LIST_HEAD(&cdev->thermal_instances);
cdev->np = np;
cdev->ops = ops;
cdev->updated = false;
cdev->device.class = &thermal_class;
cdev->devdata = devdata;
// 设置设备的sysfs,设置设备名并注册设备
thermal_cooling_device_setup_sysfs(cdev);
dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
result = device_register(&cdev->device);
// 将设备添加到thermal_cdev_list链表中,该链表保存了所有注册的cooling device
mutex_lock(&thermal_list_lock);
list_add(&cdev->node, &thermal_cdev_list);
mutex_unlock(&thermal_list_lock);
// 注册了新的cooling device,更新所有的thermal zone
bind_cdev(cdev);
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node)
if (atomic_cmpxchg(&pos->need_update, 1, 0))
thermal_zone_device_update(pos,
THERMAL_EVENT_UNSPECIFIED);
mutex_unlock(&thermal_list_lock);
return cdev;
}
6.2 cpu cooling
linux 中 cpufreq 子系统负责管理和调节 cpu 的频率,在注册 cpufreq 设备时,调用了 of_cpufreq_cooling_register
接口注册了 cpu cooling device,这样在 thermal 子系统中,可以通过调整 cpu 的频率来控制系统的温度。
thermal 子系统中,cpu cooling device 相关的接口在 “cpu_cooling.c” 中。
6.2.1 注册
注册 cpufreq 设备时,调用 of_cpufreq_cooling_register
接口注册 cpu cooling device:
struct thermal_cooling_device *
of_cpufreq_cooling_register(struct cpufreq_policy *policy)
{
struct device_node *np = of_get_cpu_node(policy->cpu, NULL);
struct thermal_cooling_device *cdev = NULL;
u32 capacitance = 0;
// 如果cpu的dts节点中配置了"#cooling-cells"字段,则将cpu注册为cooling device
if (of_find_property(np, "#cooling-cells", NULL)) {
of_property_read_u32(np, "dynamic-power-coefficient",
&capacitance);
cdev = __cpufreq_cooling_register(np, policy, capacitance);
}
of_node_put(np);
return cdev;
}
EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
实际调用了__cpufreq_cooling_register
接口实现cpu cooling device的注册:
static struct thermal_cooling_device *
__cpufreq_cooling_register(struct device_node *np,
struct cpufreq_policy *policy, u32 capacitance)
{
struct thermal_cooling_device *cdev;
struct cpufreq_cooling_device *cpufreq_cdev;
char dev_name[THERMAL_NAME_LENGTH];
unsigned int freq, i, num_cpus;
struct device *dev;
int ret;
struct thermal_cooling_device_ops *cooling_ops;
// 获取cpu device,并获取cpu频率表的条数
dev = get_cpu_device(policy->cpu);
i = cpufreq_table_count_valid_entries(policy);
// 分配并初始化 cpufreq_cooling_device 结构体
cpufreq_cdev = kzalloc(sizeof(*cpufreq_cdev), GFP_KERNEL);
cpufreq_cdev->policy = policy;
num_cpus = cpumask_weight(policy->related_cpus);
cpufreq_cdev->idle_time = kcalloc(num_cpus,
sizeof(*cpufreq_cdev->idle_time),
GFP_KERNEL);
// 申请freq_table内存
cpufreq_cdev->max_level = i - 1;
cpufreq_cdev->freq_table = kmalloc_array(i,
sizeof(*cpufreq_cdev->freq_table),
GFP_KERNEL);
// 分配设备id
ret = ida_simple_get(&cpufreq_ida, 0, 0, GFP_KERNEL);
cpufreq_cdev->id = ret;
snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
cpufreq_cdev->id);
// 根据cpu的频率表,填充freq_table
for (i = 0, freq = -1; i <= cpufreq_cdev->max_level; i++) {
freq = find_next_max(policy->freq_table, freq);
cpufreq_cdev->freq_table[i].frequency = freq;
}
// 根据电容值是否为0,也就是cpu是否可以调压,分配不同的cooling ops
if (capacitance) {
// 支持调压,将freq对应的power添加到freq_table
ret = update_freq_table(cpufreq_cdev, capacitance);
cooling_ops = &cpufreq_power_cooling_ops;
} else {
cooling_ops = &cpufreq_cooling_ops;
}
// 添加频率的qos请求
ret = freq_qos_add_request(&policy->constraints,
&cpufreq_cdev->qos_req, FREQ_QOS_MAX,
cpufreq_cdev->freq_table[0].frequency);
// 注册cooling device
cdev = thermal_of_cooling_device_register(np, dev_name, cpufreq_cdev,
cooling_ops);
// 将设备添加到cpufreq_cdev_list
mutex_lock(&cooling_list_lock);
list_add(&cpufreq_cdev->node, &cpufreq_cdev_list);
mutex_unlock(&cooling_list_lock);
return cdev;
}
6.2.2 thermal_cooling_device_ops
cpu cooling device 的 ops 有两种,区别是 cpu 是否支持调压,支持调压的 cpu cooling device 使用 cpufreq_power_cooling_ops
static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
.get_max_state = cpufreq_get_max_state,
.get_cur_state = cpufreq_get_cur_state,
.set_cur_state = cpufreq_set_cur_state,
};
static struct thermal_cooling_device_ops cpufreq_power_cooling_ops = {
.get_max_state = cpufreq_get_max_state,
.get_cur_state = cpufreq_get_cur_state,
.set_cur_state = cpufreq_set_cur_state,
.get_requested_power = cpufreq_get_requested_power,
.state2power = cpufreq_state2power,
.power2state = cpufreq_power2state,
};
6.2.3 cooling device state 控制
上面讲到,linux thermal 子系统中的 governor 根据当前温度和调温策略来调控 cooling device。
实际上,不同的 governor 根据各自的策略,更新 thermal_instance 结构体中的 taget 字段,这个target 字段就是期望的 cooling device 状态。
在 governor 的 throttle 接口中,会遍历所有的 cooling device,调用 thermal_cdev_update
接口更新 cooling device 的状态,最终实际调用的是 thermal_cooling_device_ops
结构体中的 set_cur_state
接口。
step_wise_throttle
-> thermal_cdev_update
-> cdev->ops->set_cur_state(cdev, target)
对 cpu cooling devcie 来讲,target(也就是 state) 字段就是 freq_table 的 index,直接将 cpu 频率调整到 freq_table[state] 就好:
static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
/* Request state should be less than max_level */
if (WARN_ON(state > cpufreq_cdev->max_level))
return -EINVAL;
/* Check if the old cooling action is same as new cooling action */
if (cpufreq_cdev->cpufreq_state == state)
return 0;
cpufreq_cdev->cpufreq_state = state;
// 通过 qos 接口直接调频
return freq_qos_update_request(&cpufreq_cdev->qos_req,
cpufreq_cdev->freq_table[state].frequency);
}
bug
本文基于 linux-5.4.31 源码开发和学习的thermal 框架,在实际调试中发现一个bug:
- thermal 中,
thermal_cooling_device_ops
中的set_cur_state
期望 success 返回值是 0,任何其它返回值都是fail,无论该返回值是大于0还是小于0; cpufreq_set_cur_state
接口的返回值是freq_qos_update_request
接口的返回值,但是freq_qos_update_request
接口的返回值:- 0:cpu 频率无变化;
- 1:cpu 频率有变化;
- 负值:其它失败码;
可见,按照当前的实现,当 cpu 频率被成功修改后,set_cur_state
接口的返回值是1,与 thermal 中的预期返回值是不匹配的。
而在 thermal_cdev_update
接口中是这么调用set_cur_state
的:
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
// ……
if (!cdev->ops->set_cur_state(cdev, target))
thermal_cooling_device_stats_update(cdev, target);
// ……
}
当 set_cur_state
返回值为0,也就是预期的 success,调用 thermal_cooling_device_stats_update
接口更新 sysfs 中的状态。
但是cpufreq_set_cur_state
接口成功更新 cpu freq 后的返回值是1,也就是虽然 cpu 频率被成功更新了,但是 thermal sysfs 中显示 cpu 频率状态是不匹配的。
一个小小的bug。
6.3 devfreq cooling
linux 中 devfreq 子系统负责管理和调节所以可以调频的设备的频率,在注册 devfreq 设备时,如果调用 of_devfreq_cooling_register
接口注册了 devfreq cooling device,在 thermal 子系统中,就可以通过调整这些 devfreq 设备的频率来控制系统的温度。
thermal 子系统中,devfreq cooling device 相关的接口在 “devfreq_cooling.c” 中。
6.3.1 注册
of_devfreq_cooling_register
接口调用 of_devfreq_cooling_register_power
实现 devfreq cooling device 的注册:
struct thermal_cooling_device *
of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
struct devfreq_cooling_power *dfc_power)
{
struct thermal_cooling_device *cdev;
struct devfreq_cooling_device *dfc;
char dev_name[THERMAL_NAME_LENGTH];
int err;
// 为 devfreq_cooling_device 分配内存
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
// 初始化dfc,如果支持功率操作,设置功率相关的操作函数
dfc->devfreq = df;
if (dfc_power) {
dfc->power_ops = dfc_power;
devfreq_cooling_ops.get_requested_power =
devfreq_cooling_get_requested_power;
devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
}
// 根据devfreq设备的opp接口,初始化dfc的power_table和freq_table
err = devfreq_cooling_gen_tables(dfc);
// 分配设备id
err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
dfc->id = err;
// 注册cooling device
snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
cdev = thermal_of_cooling_device_register(np, dev_name, dfc,
&devfreq_cooling_ops);
dfc->cdev = cdev;
return cdev;
}
EXPORT_SYMBOL_GPL(of_devfreq_cooling_register_power);
6.3.2 thermal_cooling_device_ops
static struct thermal_cooling_device_ops devfreq_cooling_ops = {
.get_max_state = devfreq_cooling_get_max_state,
.get_cur_state = devfreq_cooling_get_cur_state,
.set_cur_state = devfreq_cooling_set_cur_state,
};
最主要的接口是set_cur_state,通过该接口实现了 devfreq 的频率控制:
static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
struct device *dev = df->dev.parent;
int ret;
if (state == dfc->cooling_state)
return 0;
dev_dbg(dev, "Setting cooling state %lu\n", state);
if (state >= dfc->freq_table_size)
return -EINVAL;
ret = partition_enable_opps(dfc, state);
if (ret)
return ret;
dfc->cooling_state = state;
return 0;
}
接口内部直接调用 partition_enable_opps
接口实现对devfreq设备的控制;
opp接口实际是频率\电压一起调节的,对于支持功率调节的设备,在该接口中也一起控制了。
七、Thermal dts
thermal dts 主要涉及以下五种类型的 dts 节点:
- thermal sensors:温度传感器;
- cooling device:用于散热的设备;
- trip points:根据硬件属性设计的采取不同降温策略的温度点集合;
- cooling maps:表述 trip points 与 cooling device 连接关系;
- thermal zones:用于总的描述热数据信息;
7.1 thermal cooling device dts
cooling device 是调温设备的节点,有两种方法来调温:
- 第一种是降低设备的性能,也就是所谓的被动冷却,通过降低例如 cpu 的电压和频率来达到冷却效果;
- 第二种是主动冷却,例如通过风扇来降低热量;
字段:
- #cooling-cells:表示在thermal zone使用该 cooling device时,需要传入的参数个数,最小值是2,一般表示 cooling device 的最小状态和最大状态;
cpu cooling device dts:
cpu0_opp: cpu0_opp_table {
compatible = "operating-points-v2";
opp-shared;
opp_333 {
opp-hz = /bits/ 64 <333000000>;
/*opp-microvolt = <1060000 850000 1150000>;*/
clock-latency-ns = <400>;
};
opp_16 {
opp-hz = /bits/ 64 <260000e>;
clock-latency-ns = <4000>;
};
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
.....
clocks = <&clk_ap_core>;
clock-names ="clk_ap_core";
operating-points-v2 = <&cpu0_opp>;
#cooling-cells = <2>;
};
};
devfreq cooling device dts:
dmc_opp_table: dmc_opp_table {
compatible = "operating-points-v2";
opp_1066 {
opp-hz = /bits/ 64 <1066000000>;
};
opp_800 {
opp-hz = /bits/ 64 <800000000>;
};
opp_533 {
opp-hz = /bits/ 64 <533000000>;
};
opp_200 {
opp-hz = /bits/ 64 <200000000>;
};
};
dmc: dmc {
compatible ="test, test-devfreq";
operating-points-v2 = <&dmc_opp_table>;
#cooling-cells = <2>;
};
7.2 thermal sensor dts
thermal sensor devices 是 thermal zones 中提供温度属性的节点。
字段说明:
- #thermal-sensor-cells:表示在thermal zone的dts中使用该sensor时,需要传入的参数的个数。
auxadc: auxadc {
compatible ="test,test-tsadc";
#thermal-sensor-cells = <0>;
status = "okay";
};
7.3 thermal zone
7.3.1 thermal zone
thermal zone 是包含所有描述一个 thermal zone 信息的节点,包括 cooling device bindings、trips 信息子节点、描述 cooling device maps 信息子节点。
字段说明:
- polling-delay:check thermal zone 的最大 polling 延时;
- polling-delay-passive:执行 passive cooling 动作时,最大 polling 延时;
- thermal-sensors:thermal zone 使用的温度传感器;
- trips:包含一系列 trip points 节点的子节点;
- cooling-maps:包含 cooling device map nodes 的子节点;
可选字段:
- coefficients:存储系数的数组,用于表示 thermal zone 包含的多个传感器的系数关系;
- sustainable-power:在期望的温度下,thermal zone 持续的功率
7.3.2 trip
dts 中的 trip 节点用于描述温控系统中的温度触发点。
字段说明:
- temperature:表示触发点的温度阈值,单位是毫摄氏度;
- hysteresis:基于上述 temperature 属性基础上的迟滞值,是一个相对于 temperature 的相对值,用于避免频繁触发温度告警,单位是毫摄氏度;
- type:触发点的类型,有以下几种类型
- active:使能 active cooling 动作的 trip 点;
- passive:使能 passive cooling 动作的 trip 点;
- hot:高温触发点,此时需要触发警告;
- critical:关键严重触发点,表示系统已经到了临界温度,可能需要立即关机或者重启;
7.3.3 cooling-maps
cooling-maps 用于定义 thermal-zone 中使用的冷却设备与特定的温度trip的关联关系,在该节点中,可以指定在到达某个温度阈值时,应该启动哪些冷却设备,以及这些冷却设备的工作等级。
字段说明:
- trip:引用一个温度阈值节点;
- cooling-device:指定该温度阈值下的要使用的冷却设备,以及冷却设备的工作状态,<“cooling device” “minimum cooling state” “maximum cooling state”>
可选字段:
- contribution:表示当前节点的 cooling 动作对整个 thermal zone 的冷却贡献,是相对所有 contribution 总和的一个比值关系;
7.3.4 thermal zone举例
thermal-zones {
cpu_thermal: cpu_thermal {
polling-delay-passive = <100>; /* milliseconds */
polling-delay = <5000>; /* milliseconds */
thermal-sensors = <&auxadc>;
trips {
cpu_alert0: cpu_alert0 {
temperature = <70000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "passive";
};
cpu_alert1: cpu_alert1 {
temperature = <75000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "passive";
};
cpu_hot: cpu_hot {
temperature = <80000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "hot";
};
cpu_crit: cpu_crit {
temperature = <90000>; /* millicelsius */
hysteresis = <2000>; /* millicelsius */
type = "critical";
};
cooling-maps {
map0 {
trip = <&cpu_alert0>;
cooling-device =
<&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&dmc THERMAL_NO_LIMIT 2>;
};
map1 {
trip = <&cpu_alert1>;
cooling-device =
<&cpu 1 1>,
<&dmc 2 THERMAL_NO_LIMIT>;
};
};
};
};