一、概述
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 平台的开发参考文档,这些注释都可以在源码里找到答案,到具体源码分析时读者应该会有更深刻的理解。