To know Linux-thermal drivers
Linux Thermal 是 Linux 系統下溫度控制相關的模組,主要用來控制系統執行過程中晶片產生的熱量,使晶片溫度和裝置外殼溫度維持在一個安全、舒適的範圍。
那下面我們就來一起看看對於溫度控制這樣一個需求,Linux 核心是怎麼實現的。
Thermal 的主要框架
要實現一個溫度控制的需求,試想一下我們是不是最少要有獲取溫度的裝置和控制溫度的裝置這兩個最基本的東西?當然附帶的也會產生一些使用溫度控制裝置的策略。
那上面這些東西在 Linux Thermal 框架中怎麼體現呢?通過閱讀原始碼我們發現程式碼中對上面的東西進行了一些抽象。
- 獲取溫度的裝置:在 Thermal 框架中被抽象為 Thermal Zone Device;
- 控制溫度的裝置:在 Thermal 框架中被抽象為 Thermal Cooling Device;
- 控制溫度策略:在 Thermal 框架中被抽象為 Thermal Governor;
Thermal Zone Device
上面說到 Thermal Zone Device 是獲取溫度裝置的抽象,怎麼抽象的?終究我們還是要 RTFSC。
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; /* 輪詢時間 */ int passive_delay; int polling_delay; int temperature; int last_temperature; int emul_temperature; int passive; unsigned int forced_passive; /* 裝置的操作函式 */ struct thermal_zone_device_ops *ops; const struct thermal_zone_params *tzp; struct thermal_governor *governor; struct list_head thermal_instances; struct idr idr; struct mutex lock; struct list_head node; /* 用來迴圈處理的 delayed_work */ 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 *, unsigned long *); 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, unsigned long *); int (*set_trip_temp) (struct thermal_zone_device *, int, unsigned long); int (*get_trip_hyst) (struct thermal_zone_device *, int, unsigned long *); int (*set_trip_hyst) (struct thermal_zone_device *, int, unsigned long); int (*get_crit_temp) (struct thermal_zone_device *, unsigned long *); int (*set_emul_temp) (struct thermal_zone_device *, unsigned long); int (*get_trend) (struct thermal_zone_device *, int, enum thermal_trend *); int (*notify) (struct thermal_zone_device *, int, enum thermal_trip_type); };
通過程式碼我們可以看到,一個能提供溫度的裝置操作函式主要有 : 繫結函式、獲取溫度函式、獲取觸發點溫度函式。
繫結函式 : Thermal core 用來繫結用的 , 這個後面會講 ;
獲取溫度函式 : 獲取裝置溫度用的,這個也好理解 , 一般 SOC 內部會有溫度感測器提供溫度,有些熱敏電阻通過 ADC 也算出溫度,這個函式就是取這些溫度值 ;
獲取觸發點溫度函式 : 這個是什麼用來做什麼呢 ? 這個其實是 thermal 框架裡面一個關鍵點,因為要控制溫度,那麼什麼時候控制就需要有東西來描述,
描述什麼時候控制的東西就是觸發點,每個 thermal zone device 會定義很多觸發點,那麼每個觸發點的溫度就是通過該函式獲得;
Thermal Cooling Devices
Thermal Cooling Device 是可以降溫裝置的抽象,能降溫的裝置比如風扇,這些好理解,但是想 CPU,GPU 這些 Cooling devices 怎麼理解呢?
其實降溫可以從兩方面來理解,一個是加快散熱,另外一個就是降低產熱量。風扇,散熱片這些是用來加快散熱,CPU,GPU 這些 Cooling devices 是通過降低產熱來降溫。
那程式碼是怎麼將這兩個方式統一起來呢?
struct thermal_cooling_device {
int id;
char type[THERMAL_NAME_LENGTH];
struct device device;
struct device_node *np;
void *devdata;
/* cooling device 操作函式 */
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);
};
Thermal Cooling device 抽象的方式是,認為所有的能降溫的裝置有很多可以單獨控制的狀態。例如,風扇有不同的風速狀態,
CPU/GPU Cooling device 有不同最大執行頻率狀態,這樣當溫度高了之後通過調整這些狀態來降低溫度;
Thermal Governor
Thermal Governor 是降溫策略的一個抽象 , 主要是根據溫度來選擇 thermal cooling devices 等級的方法,舉個簡單的例子,當前的溫度升高速很快,選擇風扇3檔風,溫度升高不快,選擇1檔風。這就是一個 Governor。
/**
* struct thermal_governor - structure that holds thermal governor information
* @name: name of the governor
* @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 (*throttle)(struct thermal_zone_device *tz, int trip);
struct list_head governor_list;
};
很簡單,所有的策略都通過 throttle 這個函式實現,核心已經實現了一些策略,step_wise, user_space, power_allocator, bang_bang 等具體實現演算法細節就不展開;
Thermal Core
有了獲取溫度的裝置,有了溫控控制的裝置,有了控制方法,Thermal Core 就負責把這些整合在一起。下面看一下整合的簡單流程。
1.註冊函式 ,thermal Core 通過對外提供註冊的介面,讓 thermal zone device、thermal cooling device、thermal governor 註冊進來。
struct thermal_zone_device *thermal_zone_device_register(const char *, int, int,
void *, struct thermal_zone_device_ops *,
const struct thermal_zone_params *, int, int);
struct thermal_cooling_device *thermal_cooling_device_register(char *, void *,
const struct thermal_cooling_device_ops *);
int thermal_register_governor(struct thermal_governor *);
2.Thermal zone/cooling device 註冊的過程中 thermal core 會呼叫繫結函式,繫結的過程最主要是一個 cooling device 繫結到一個 thremal_zone 的觸發點上
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz)
{
struct thermal_instance *dev;
struct thermal_instance *pos;
struct thermal_zone_device *pos1;
struct thermal_cooling_device *pos2;
unsigned long max_state;
int result;
...
/* thermal_instace 就是繫結之後的例項 */
dev =
kzalloc(sizeof(struct thermal_instance), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->tz = tz;
dev->cdev = cdev;
dev->trip = trip;
dev->upper = upper;
...
sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
if (result)
goto release_idr;
sprintf(dev->attr_name, "cdev%d_trip_point", dev->id);
sysfs_attr_init(&dev->attr.attr);
...
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
result = -EEXIST;
break;
}
if (!result) {
/* 繫結完了就新增到連結串列中 */
list_add_tail(&dev->tz_node, &tz->thermal_instances);
list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
}
...
return result;
}
3.Thermal core 使能 delayed_work 迴圈處理 , 使整個 thermal 控制流程運轉起來。
static void thermal_zone_device_check(struct work_struct *wrok)
{
struct thermal_zone_device *tz = container_of(work, struct
thermal_zone_device,
poll_queue.work);
/* 處理函式 */
thermal_zone_device_update(tz);
}
void thermal_zone_device_update(struct thermal_zone_device *tz)
{
int count;
if (!tz->ops->get_temp)
return;
/* 更新溫度 */
update_temperature(tz);-
for (count = 0; count < tz->trips; count++)
/* 處理觸發點,這裡面就會調到具體的 governor */
handle_thermal_trip(tz, count);
}
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, int delay)
{
if (delay > 1000)
/* 更改 delayed_work 下次喚醒時間完成輪詢 */
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
round_jiffies(msecs_to_jiffies(delay)));
else if (delay)
mod_delayed_work(system_freezable_wq, &tz->poll_queue,
msecs_to_jiffies(delay));
else
cancel_delayed_work(&tz->poll_queue);
}
當溫度升高超過溫度觸發點的話,就會使能對應的 cooling device 進行降溫處理,至此 Linux Thermal 相關的一些基本框架就介紹完了。