Linux Thermal机制源码分析之框架概述
Thermal,中文意思是热的、保暖的。在 Linux 内核中,Thermal 特指一套关于温控机制的驱动框架,其目的是为了防止 SoC 等硬件芯片因过热而造成系统不稳定,甚至缩减芯片寿命。Thermal 框架是在软件层面上对自然界散热系统的抽象。试想一下空调的工作机制,假设场景是夏天,室内温度 35 度,用户设定目标温度 25 度。对房间降温首先得知道房间里当前温...
一、概述
Thermal,中文意思是热的、保暖的。在 Linux 内核中,Thermal 特指一套关于温控机制的驱动框架,其目的是为了防止 SoC 等硬件芯片因过热而造成系统不稳定,甚至缩减芯片寿命。
Thermal 框架是在软件层面上对自然界散热系统的抽象。试想一下空调的工作机制,假设场景是夏天,室内温度 35 度,用户设定目标温度 25 度。对房间降温首先得知道房间里当前温度是多少,这个工作需要温度传感器来完成。当温度传感器探知室内温度并传给空调内部的 MCU(姑且认为空调的控制系统是由一颗内嵌的 MCU 来完成的吧) 后,MCU 得知当前温差是10度,比较大,所以加大功率,让房间迅速降温(我们知道空调内部的降温设备是雪种)。一段时间后,温度降至 28 度,这个时候 MCU 开始减小功率,降温速度开始趋缓,直至稳定在 25 度。当然,空调实际的工作方式不一定和上面描述的一模一样,但是基本原理是这样。我们试图通过空调的例子抽取温控系统的必要组件,然后过渡到 Linux 内核中的 Thermal 框架:
1、既然要控制房间温度,当然要能知道房间当前的温度,这个必要组件就是温度传感器;
2、降温或者升温,需要实际的设备来支撑,这个必要组件就是雪种;
3、温差大,加大功率以便迅速降温,温差小,降低功率省电,这个必要组件就是温控策略,或者叫温控算法;
4、整个温控系统需要有一个类似大脑的设备来统筹管理,这个必要组件就是 MCU。
类比到 Thermal 框架(我们主要考虑 CPU/GPU 的温控),其必要组件和上面的空调例子十分相似:
1、Thermal sensor driver,SoC 内部 CPU 和 GPU 的旁边通常会有用于获取它们温度的传感器,比如 tsadc(Temperature Sensor ADC)。关于传感器的更多细节我们在 sensor driver 章节再进行深入探讨。
2、Thermal cooling device,降温设备,比如风扇。这里有点特殊的是,CPU 和 GPU 不仅是发热设备(即需要实施温控策略的设备),也可以是降温设备,当我们降低 CPU/GPU 的运行频率的时候,它们就在充当降温设备。降低产热量即是在降温。
3、Thermal governer,温控策略,Linux 内核中的温控策略要比上面的空调控制精细得多,而且也提供了多种策略。
4、Thermal core,组织并管理上面三个组件,并通过 sysfs 和用户空间交互。
Thermal 的软件框架大致如下:
二、代码路径
本文源码分析基于瑞芯微 RK3399 Android7.1(kernel 4.4)平台,除了 sensor driver 是平台驱动工程师写的外,其余组件均为 Linux 内核提供。
Thermal sensor driver 代码:
drivers/thermal/rockchip_thermal.c /* tsadc驱动 */
Thermal cooling device 相关代码:
drivers/thermal/devfreq_cooling.c
drivers/thermal/cpu_cooling.c
Thermal governor 相关代码:
drivers/thermal/power_allocator.c /* power allocator 温控策略 */
drivers/thermal/step_wise.c /* step wise 温控策略 */
drivers/thermal/fair_share.c /* fair share 温控策略 */
drivers/thermal/user_space.c /* userspace 温控策略 */
Thermal core 相关代码:
drivers/thermal/thermal_core.c
drivers/thermal/of_thermal.c
三、重要结构体
这些重要结构体,这里先只做一个简要的介绍,混个脸熟,到后面各个组件的源码分析时再来进行详细的成员解析。
1、sensor driver相关
/**
* struct rockchip_thermal_sensor - hold the information of thermal sensor
* @thermal: pointer to the platform/configuration data
* @tzd: pointer to a thermal zone
* @id: identifier of the thermal sensor
*/
struct rockchip_thermal_sensor {
struct rockchip_thermal_data *thermal;
struct thermal_zone_device *tzd;
int id;
};
struct rockchip_thermal_sensor:RK 平台上该结构体代表了一个 tsadc;
struct rockchip_thermal_data:见下面的介绍;
struct thermal_zone_device:一个 tsadc 会和一个 thermal zone 绑定;
int id:该 tsadc 的编号,一般来说 RK 的 SoC 内部有两个 tsadc;
/**
* struct rockchip_thermal_data - hold the private data of thermal driver
* @chip: pointer to the platform/configuration data
* @pdev: platform device of thermal
* @reset: the reset controller of tsadc
* @sensors[SOC_MAX_SENSORS]: the thermal sensor
* @clk: the controller clock is divided by the exteral 24MHz
* @pclk: the advanced peripherals bus clock
* @grf: the general register file will be used to do static set by software
* @regs: the base address of tsadc controller
* @tshut_temp: the hardware-controlled shutdown temperature value
* @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
*/
struct rockchip_thermal_data {
const struct rockchip_tsadc_chip *chip;
struct platform_device *pdev;
struct reset_control *reset;
struct rockchip_thermal_sensor sensors[SOC_MAX_SENSORS];
struct clk *clk;
struct clk *pclk;
struct regmap *grf;
void __iomem *regs;
int tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
};
struct rockchip_thermal_data:sensor driver 的私有数据,详见注释。
/**
* struct rockchip_tsadc_chip - hold the private data of tsadc chip
* @chn_id[SOC_MAX_SENSORS]: the sensor id of chip correspond to the channel
* @chn_num: the channel number of tsadc chip
* @tshut_temp: the hardware-controlled shutdown temperature value
* @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
* @initialize: SoC special initialize tsadc controller method
* @irq_ack: clear the interrupt
* @get_temp: get the temperature
* @set_alarm_temp: set the high temperature interrupt
* @set_tshut_temp: set the hardware-controlled shutdown temperature
* @set_tshut_mode: set the hardware-controlled shutdown mode
* @table: the chip-specific conversion table
*/
struct rockchip_tsadc_chip {
/* The sensor id of chip correspond to the ADC channel */
int chn_id[SOC_MAX_SENSORS];
int chn_num;
/* The hardware-controlled tshut property */
int tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
/* Chip-wide methods */
void (*initialize)(struct regmap *grf,
void __iomem *reg, enum tshut_polarity p);
void (*irq_ack)(void __iomem *reg);
void (*control)(void __iomem *reg, bool on);
/* Per-sensor methods */
int (*get_temp)(struct chip_tsadc_table table,
int chn, void __iomem *reg, int *temp);
void (*set_alarm_temp)(struct chip_tsadc_table table,
int chn, void __iomem *reg, int temp);
void (*set_tshut_temp)(struct chip_tsadc_table table,
int chn, void __iomem *reg, int temp);
void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m);
/* Per-table methods */
struct chip_tsadc_table table;
};
struct rockchip_tsadc_chip:详见注释。
RK 的 sensor driver 为了兼容他们家很多 SoC 的 tsadc,把差异性的东西抽出来。比如那些函数指针,由于寄存器地址的不一样函数体的具体内容也会不一样,如 RK3399 和 PX30 之间。再比如由于 SoC 制程不一样,默认的关机温度也可能不一样。
2、governor相关
/**
* struct thermal_governor - structure that holds thermal governor information
* @name: name of the governor
* @bind_to_tz: callback called when binding to a thermal zone. If it
* returns 0, the governor is bound to the thermal zone,
* otherwise it fails.
* @unbind_from_tz: callback called when a governor is unbound from a thermal zone.
* @throttle: callback called for every trip point even if temperature is
* below the trip point temperature
* @governor_list: node in thermal_governor_list (in thermal_core.c)
*/
struct thermal_governor {
char name[THERMAL_NAME_LENGTH];
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;
};
struct thermal_governor:用来描述一个 governor(即温控策略) 信息。
内核目前有五种 governor:
1、power_allocator:引⼊ PID(⽐例-积分-微分)控制,根据当前温度,动态给各 cooling device 分配 power,并将 power 转换为频率,从而达到根据温度限制频率的效果。
2、step_wise:根据当前温度,cooling device 逐级降频。
3、fair share:频率档位⽐较多的 cooling device 优先降频。
4、bang bang:两点温度调节,可用于 cooling device 有风扇的场景。
5、userspace:用户空间控制。
RK 平台统一使用 power_allocator 策略。
3、cooling device相关
struct thermal_cooling_device {
int id;
char type[THERMAL_NAME_LENGTH];
struct device device;
struct device_node *np;
void *devdata;
const struct thermal_cooling_device_ops *ops;
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;
};
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 *);
int (*state2power) (struct thermal_cooling_device *,
struct thermal_zone_device *, unsigned long, u32 *);
int (*power2state) (struct thermal_cooling_device *,
struct thermal_zone_device *, u32, unsigned long *);
};
struct thermal_cooling_device:用来描述一个 cooling device(即降温设备) 信息,并将函数操作集抽取出来。
4、thermal zone
/**
* struct thermal_zone_device - structure for a thermal zone
* @id: unique id number for each thermal zone
* @type: the thermal zone device type
* @device: &struct device for this thermal zone
* @trip_temp_attrs: attributes for trip points for sysfs: trip temperature
* @trip_type_attrs: attributes for trip points for sysfs: trip type
* @trip_hyst_attrs: attributes for trip points for sysfs: trip hysteresis
* @devdata: private pointer for device private data
* @trips: number of trip points the thermal zone supports
* @trips_disabled; bitmap for disabled trips
* @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)
* @temperature: current temperature. This is only for core code,
* drivers should use thermal_zone_get_temp() to get the
* current temperature
* @last_temperature: previous temperature read
* @emul_temperature: emulated temperature when using CONFIG_THERMAL_EMULATION
* @passive: 1 if you've crossed a passive trip point, 0 otherwise.
* @forced_passive: If > 0, temperature at which to switch on all ACPI
* processor cooling devices. Currently only used by the
* step-wise governor.
* @need_update: if equals 1, thermal_zone_device_update needs to be invoked.
* @ops: operations this &thermal_zone_device supports
* @tzp: thermal zone parameters
* @governor: pointer to the governor for this thermal zone
* @governor_data: private pointer for governor data
* @thermal_instances: list of &struct thermal_instance of this thermal zone
* @idr: &struct idr to generate unique id for this zone's cooling
* devices
* @lock: lock to protect thermal_instances list
* @node: node in thermal_tz_list (in thermal_core.c)
* @poll_queue: delayed work for polling
*/
struct thermal_zone_device {
int id;
char type[THERMAL_NAME_LENGTH];
struct device device;
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;
int polling_delay;
int temperature;
int last_temperature;
int emul_temperature;
int passive;
unsigned int forced_passive;
atomic_t need_update;
struct thermal_zone_device_ops *ops;
struct thermal_zone_params *tzp;
struct thermal_governor *governor;
void *governor_data;
struct list_head thermal_instances;
struct idr idr;
struct mutex lock;
struct list_head node;
struct delayed_work poll_queue;
};
struct thermal_zone_device_ops {
int (*bind) (struct thermal_zone_device *,
struct thermal_cooling_device *);
int (*unbind) (struct thermal_zone_device *,
struct thermal_cooling_device *);
int (*get_temp) (struct thermal_zone_device *, int *);
int (*get_mode) (struct thermal_zone_device *,
enum thermal_device_mode *);
int (*set_mode) (struct thermal_zone_device *,
enum thermal_device_mode);
int (*get_trip_type) (struct thermal_zone_device *, int,
enum thermal_trip_type *);
int (*get_trip_temp) (struct thermal_zone_device *, int, int *);
int (*set_trip_temp) (struct thermal_zone_device *, int, int);
int (*get_trip_hyst) (struct thermal_zone_device *, int, int *);
int (*set_trip_hyst) (struct thermal_zone_device *, int, int);
int (*get_crit_temp) (struct thermal_zone_device *, int *);
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);
};
struct thermal_zone_device:一个 thermal zone 是根据 dts 里的配置一步步解析并构建的,包含了很多信息,比如服务于该 thermal zone 的 tsadc,服务于该 thermal zone 的降温设备,该 thermal zone 所用的 governor,以及 thermal 机制工作时所需的一些参数,等等。更多细节在源码分析部分再来说明。
通常,RK 平台上 thermal zone 的 dts 配置格式如下。其它平台应该和这个大同小异,因为都要基于 thermal core 来配置。
thermal_zones: thermal-zones {
/* 一个节点对应一个thermal zone,并包含温控策略相关参数 */
soc_thermal: soc-thermal {
/* 温度高于trip-point-0指定的值,每隔20ms获取一次温度 */
polling-delay-passive = <20>; /* milliseconds */
/* 温度低于trip-point-0指定的值,每隔1000ms获取一次温度 */
polling-delay = <1000>; /* milliseconds */
/* 温度等于trip-point-1指定的值时,系统分配给cooling device的能量 */
sustainable-power = <1000>; /* milliwatts */
/* 当前thermal zone通过tsadc0获取温度 */
thermal-sensors = <&tsadc 0>;
/* trips包含不同温度阈值,不同的温控策略,配置不一定相同 */
trips {
/*
* 温控阈值,超过该值温控策略开始工作作,但不一定马上限制频率,
* power小到一定程度才开始限制频率
*/
threshold: trip-point-0 {
/* 超过70摄氏度,温控策略开始工作,并且70度也是tsadc触发中断的一个阈值 */
temperature = <70000>; /* millicelsius */
/* 温度低于temperature-hysteresis时触发中断,当前未实现,但框架要求必须填 */
hysteresis = <2000>; /* millicelsius */
type = "passive"; /* 表示超过该温度值时,使用polling-delay-passive */
};
/* 温控目标温度,期望通过降频使得芯片不超过该值 */
target: trip-point-1 {
/* 期望通过降频使得芯片不超过85摄氏度,并且85度也是tsadc触发中断的一个阈值 */
temperature = <85000>; /* millicelsius */
/* 温度低于temperature-hysteresis时触发中断,当前未实现,但框架要求必须填 */
hysteresis = <2000>; /* millicelsius */
type = "passive"; /* 表示超过该温度值时,使用polling-delay-passive */
};
/* 过温保护阈值,如果降频后温度仍然上升,那么超过该值后,让系统重启 */
soc_crit: soc-crit {
/* 超过115摄氏度重启,并且115度也是tsadc触发中断的一个阈值 */
temperature = <115000>; /* millicelsius */
/* 温度低于temperature-hysteresis时触发中断,当前未实现,但框架要求必须填 */
hysteresis = <2000>; /* millicelsius */
type = "critical"; /* 表示超过该温度值时,重启 */
};
};
/* cooling device配置节点,每个子节点代表一个cooling device */
cooling-maps {
map0 {
/*
* 表示在target trip下,该cooling device才起作用,
* 对于power allocater策略必须填target
*/
trip = <&target>;
/* A53做为cooloing device, THERMAL_NO_LIMIT不起作用,但必须填 */
cooling-device = <&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
/* 计算功耗时乘以4096/1024倍,用于调整降频顺序和尺度 */
contribution = <4096>;
};
map1 {
/*
* 表示在target trip下,该cooling device才起作用,
* 对于power allocater策略必须填target
*/
trip = <&target>;
/* A72做为cooloing device, THERMAL_NO_LIMIT不起作用,但必须填 */
cooling-device = <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
/* 计算功耗时乘以1024/1024倍,用于调整降频顺序和尺度 */
contribution = <1024>;
};
map2 {
/*
* 表示在target trip下,该cooling device才起作用,
* 对于power allocater策略必须填target
*/
trip = <&target>;
/* GPU做为cooloing device, THERMAL_NO_LIMIT不起作用,但必须填 */
cooling-device = <&gpu THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
/* 计算功耗时乘以4096/1024倍,用于调整降频顺序和尺度 */
contribution = <4096>;
};
};
};
/* 一个节点对应一个thermal zone,并包含温控策略相关参数,当前thermal zone只用于获取温度 */
gpu_thermal: gpu-thermal {
/* 包含温控策略配置的情况下才起作用,框架要求必须填 */
polling-delay-passive = <100>; /* milliseconds */
/* 每隔1000ms获取一次温度 */
polling-delay = <1000>; /* milliseconds */
/* 当前thermal zone通过tsadc1获取温度 */
thermal-sensors = <&tsadc 1>;
};
};
上面每个子结点和每条属性的注释摘自 RK 平台的开发参考文档,这些注释都可以在源码里找到答案,到具体源码分析时读者应该会有更深刻的理解。
更多推荐
所有评论(0)