作者简介:大Q在某半导体公司任职,主要从事硬件、芯片、驱动、软件,整体方案及架构的功耗设计。喜欢研究linux,liteos等系统的功耗设计思路,以及业界如何结合实际场景进行软硬件方案设计。
DVFS全称Dynamic Voltage and Frequency Scaling,本章主要讲解CPU的DVFS。
Linux 的CPU调频调压由cpufreq完成,cpufreq需要拆成两个词看cpu-freq,通过字面意思可知,cpufreq和CPU及频率有关。随着半导体工艺的演进,芯片性能越来越强,软件迭代对CPU性能的需求也越来越大,CPU频率也越来越高。如果一直让CPU运行在最高频率下,功耗、发热等问题也会随之而来,本章我们讲解CPU调频调压的设计与实现。
13.1 Linux cpufreq的设计与实现
本节,我们主要聚焦对Linux 内核cpufreq的实现机制进行分析,包括配置、主要结构体、主要函数、工作时序等。
13.1.1 架构设计概览
DVFS在低功耗软件栈中的位置如图13-1所示,属于非睡眠形式的动态功耗控制方式。
图13-1 DVFS在低功耗软件栈中的位置
13.1.2 模块功能详解
Linux的cpufreq框架用一句话概括就是基于软硬件约束通过一定的策略完成CPU频率的调整。这里软硬件配置如频率范围、CPU个数等由cpufreq驱动初始化时通过相应流程配置,主要由policy模块承载,同时,policy模块也管理governor和driver的联系等事项。策略主要指governor模块即基于什么调频,kernel默认的governor有performance、powersave、conservative、ondemand、userspace以及目前默认应用的schedutil,最后完成CPU频率配置的模块就是driver了,频率配置可以调用CLK模块接口完成,也可自行根据芯片配置流程完成,如果clk实现较好,建议通过CLK模块完成。
调频调压均支持的CPU(其他模块有同样约束)一般有如下配置约束:升频时,如果需要升压,需要先完成电压配置(记起regulator模块没)并等待电压稳定后再进行频率配置,降压时,需要先降低频率,然后再配置降压。这是由硬件决定的,先频率高于额定电压时,相当于CPU在超频运行,不一定会出问题,出问题也是千奇八怪,难以定位,还是按照芯片约束来吧。
另外,cpufreq框架还支持notify机制,在频率发生变化前后调用通知,对于需要感知CPU频率变化的模块,收到通知事件时,可以进行对应配置。
13.1.3 配置信息解析
Linux cpufreq依赖如下特性宏:
CONFIG_CPU_FREQ=y
CONFIG_CPU_FREQ_GOV_ATTR_SET=y
CONFIG_CPU_FREQ_GOV_COMMON=y
CONFIG_CPU_FREQ_STAT=y
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE=y
CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y
CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE=y
CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
CONFIG_CPUFREQ_DT=y
CONFIG_CPUFREQ_DT_PLATDEV=y
cpufreq的实现主要在如下文件中:drivers/cpufreq/cpufreq.c、cpufreq-dt.c、cpufreq-dt-platdev.c、cpufreq_governor.c、cpufreq_ondemand.c、cpufreq_stats.c、freq_table.c、cpufreq_governor_attr_set.c等
13.1.4 主要数据类型
Linux cpufreq和内核绝大多数框架一样,提供了通用机制,便于驱动开发人员专注驱动开发,但我们主要了解其核心思路,便于在非Linux系统中实现并应用自己的CPU调频模块。
1)cpufreq框架的core主要完成sysfs接口封装、driver/governor/policy逻辑串联及driver驱动接口封装。
2)governor模块主要封装governor统一接口,提供governor注册。
3)policy模块,具体也不能说是个模块,但cpufreq driver与governor的关联、管理都离不开policy模块,阅读源码时会感觉policy有点混乱哪里都有它的身影,又有点语焉不详,其实不妨碍实现自己的cpufreq。
4)driver主要功能就是完成最终的频率调整(电压调整,如果支持的话)。
其他模块这就不细述了,下面我们结合源码了解各模块功能及cpufreq是如何工作的。
1 cpufreq_policy结构体
struct cpufreq_policy 是cpufreq core提供的非常重要的结构体,下面讲解主要成员含义:
cpus及related_cpus表示当前policy管理的cpu,cpus是当前处于online状态的,related_cpus表示所有的的包含online/offline的。
CPU表示当前管理policy的cpu id,若多个CPU共用一个policy,只需要一个CPU进行管理即可,在该CPU下线时,还需要更新管理CPU。
clk 表示当前policy使用的clk句柄。
cpuinfo表示CPU设计的最大最小频率。
min/max/cur表示当前policy支持的最大最小及当前频率。
Governor/governor_data表示当前policy使用的governor及其私有数据。
freq_table当前CPU支持的频率表。
driver_data表示driver的私有数据。
结构体定义如下:
struct cpufreq_policy {
cpumask_var_t cpus; /* 只有Online的 CPUs才使用*/
cpumask_var_t related_cpus; /* Online + Offline CPUs */
unsigned int cpu; /* 使用这个策略的cpu,必须是online的*/
struct clk *clk;
struct cpufreq_cpuinfo cpuinfo;/* see above */
unsigned int min; /* in kHz */
unsigned int max; /* in kHz */
unsigned int cur; /* in kHz, 只有在cpufreq governors 被使用时踩需要 */
unsigned int policy; /* see above */
struct cpufreq_governor *governor; /* see below */
void *governor_data;
struct cpufreq_frequency_table *freq_table;
void *driver_data;
};
通过对policy结构体主要成员的介绍,我们知道policy主要用于配置CPU的调频约束以及governor的管理。
2 governor相关数据结构
governor链表,用于存放所有注册的governor节点。
static LIST_HEAD(cpufreq_governor_list);
接下来介绍下governor的主要结构体struct cpufreq_governor ,主要给出governor唯一名字及API回调。
#define CPUFREQ_DBS_GOVERNOR_INITIALIZER(_name_)
{
.name = _name_,
.flags = CPUFREQ_GOV_DYNAMIC_SWITCHING,
.owner = THIS_MODULE,
.init = cpufreq_dbs_governor_init,
.exit = cpufreq_dbs_governor_exit,
.start = cpufreq_dbs_governor_start,
.stop = cpufreq_dbs_governor_stop,
.limits = cpufreq_dbs_governor_limits,
}
struct cpufreq_governor {
char name[CPUFREQ_NAME_LEN];
int (*init)(struct cpufreq_policy *policy);
void (*exit)(struct cpufreq_policy *policy);
int (*start)(struct cpufreq_policy *policy);
void (*stop)(struct cpufreq_policy *policy);
void (*limits)(struct cpufreq_policy *policy);
ssize_t (*show_setspeed) (struct cpufreq_policy *policy,
char *buf);
int (*store_setspeed) (struct cpufreq_policy *policy,
unsigned int freq);
struct list_head governor_list;
struct module *owner;
u8 flags;
};
/* Governor flags */
/* For governors which change frequency dynamically by themselves */
#define CPUFREQ_GOV_DYNAMIC_SWITCHING BIT(0)
/* For governors wanting the target frequency to be set exactly */
#define CPUFREQ_GOV_STRICT_TARGET BIT(1)
governor模块提供了一个统一初始化宏用于对其变量进行初始化,如宏CPUFREQ_DBS_GOVERNOR_INITIALIZE的定义实现,实际使用的结构体为struct cpufreq_governor,governor模块是为了屏蔽各种governor和cpufreq关联而实现的。结构体相关成员变量含义如下所示:
name:当前初始化governor的名字,如“ondemand”、“conservative”等。
init/exit等:governor初始化、注销或切换时cpufreq core调用的流程。
governor_list:各种governor注册时挂接链表,现在已不怎么使用了。
flags:当前governor策略,见上述注释。
governor模块还有一个核心结构体struct dbs_governor,其定义如下:
struct dbs_governor {
struct cpufreq_governor gov;
struct kobj_type kobj_type;
struct dbs_data *gdbs_data;
unsigned int (*gov_dbs_update)(struct cpufreq_policy *policy);
struct policy_dbs_info *(*alloc)(void);
void (*free)(struct policy_dbs_info *policy_dbs);
int (*init)(struct dbs_data *dbs_data);
void (*exit)(struct dbs_data *dbs_data);
void (*start)(struct cpufreq_policy *policy);
};
gov:上文中CPUFREQ_DBS_GOVERNOR_INITIALIZER部分。
gdbs_data:当前governor调频约束,如负载阈值,采样周期配置等。
gov_dbs_update:governor更新负载及触发频率配置的回调,governor的精髓在这里,后续讲解。
3 driver相关数据结构
cpufreq_driver类型的变量定义:
static struct cpufreq_driver *cpufreq_driver;
core提供的driver结构体,用于core对默认driver的关联及管理。
struct cpufreq_driver {
char name[CPUFREQ_NAME_LEN];
u16 flags;
void *driver_data;
/*所有的驱动都会使用 */
int (*init)(struct cpufreq_policy *policy);
int (*verify)(struct cpufreq_policy_data *policy);
int (*setpolicy)(struct cpufreq_policy *policy);
int (*target)(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation); /* Deprecated */
int (*target_index)(struct cpufreq_policy *policy,
unsigned int index);
unsigned int (*fast_switch)(struct cpufreq_policy *policy,
unsigned int target_freq);
/*缓存并返回驱动程序支持的最低频率大于或等于目标频率。并不设置频率,只有target()才会设置频率。*/
unsigned int (*resolve_freq)(struct cpufreq_policy *policy,
unsigned int target_freq);
/*仅适用于未设置target_index()和CPUFREQ_ASYNC_NOTIFICATION的驱动程序。Get_intermediate应该返回一个稳定的中间频率在跳转到对应'index'的频率之前,target_intermediate()应该将CPU设置为该频率。core将负责发送通知,而驱动程序不必在target_intermediate()或者 target_index()中处理它们。驱动程序可以从get_intermediate()返回'0',以防他们不希望切换到某个目标频率的中间频率。在这种情况下,core将直接调用->target_index()。 */
unsigned int (*get_intermediate)(struct cpufreq_policy *policy,
unsigned int index);
int (*target_intermediate)(struct cpufreq_policy *policy,
unsigned int index);
unsigned int (*get)(unsigned int cpu);
/* 更新策略限值(policy limits). */
void (*update_limits)(unsigned int cpu);
int (*bios_limit)(int cpu, unsigned int *limit);
int (*online)(struct cpufreq_policy *policy);
int (*offline)(struct cpufreq_policy *policy);
int (*exit)(struct cpufreq_policy *policy);
void (*stop_cpu)(struct cpufreq_policy *policy);
int (*suspend)(struct cpufreq_policy *policy);
int (*resume)(struct cpufreq_policy *policy);
void (*ready)(struct cpufreq_policy *policy);
struct freq_attr **attr;
bool boost_enabled;
int (*set_boost)(struct cpufreq_policy *policy, int state);
};
主要成员属性见加粗部分。
name:驱动唯一名字。
flags:用于cpufreq部分功能控制,详见cpufreq.h,注释较清晰。
Init:driver注册时,由core调用的初始化接口,一般主要用于频率表的创建及policy的填充。
verify:主要对policy内配置及CPU频率约束进行验证,保证policy在CPU硬件约束范围之内。
target/target_index:driver实现最终调频的接口,内部可以自行实现或调用CLK接口。
suspend/resume:系统DPM时回调接口。
编辑:黄飞
评论
查看更多