CPU调频调压的设计与实现

处理器/DSP

891人已加入

描述

作者简介:大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所示,属于非睡眠形式的动态功耗控制方式。

cpu

图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时回调接口。

编辑:黄飞

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分