linux thermal 温控子系统

一、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>;
            };
        };
    };
};
  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值