目录
一 应用背景
对于可以调频的设备,我们可以通过在不同场景和需求下调节设备的频率,可以达到节省功耗的目的。CPU有单独的cpufreq框架来控制和管理其频率,但是不兼容普通的设备。对于普通设备,我们可以通过devfreq 框架来方便地实现调频操作。
二 软件框架介绍
devfreq框架存在的意义,是为了将调频逻辑的公共部分,比如数据结构,调频方法等抽象出来,减少重复代码的产生,方便驱动工程师实现设备的调频操作。
我们这里将有调频需求的设备称为device_freq,以便后续的讨论。有了devfreq框架,驱动工程师只需要按照devfreq框架提供的函数原型,实现具体设备的具体调频操作,同时选取合适的governor,并将device_freq和底层调频方法一同注册进devfreq框架,就能够实现调频。
这里的governor ,其实指的是不同的调频策略。所谓的调频策略,包括什么时候对设备进行调频,以及将设备的频率调整到多大。不同的设备,有不同的调频策略,因此devfreq将设备的调频策略抽象出来,以供设备选择。具体的设备也可以根据自己的需求,实现自己的governor,并注册进devfreq框架。以内核提供的最简单的userspace_governor为例 ,其调频策略就很简单——调频的时机是当用户通过给定的文件节点输入目标频率F时,就执行调频操作;且将设备的频率调整为F。
以上提到的两个概念,device_freq和 governor,是devfreq框架的两个核心主题,这里做一个总结:
1. device_freq,是需要调频的设备,需要通过devfreq框架提供的接口进行注册;
2. governor,是具体的调频策略,也需要通过devfreq框架提供的接口进行注册;governor可由用户自己实现,也可以选用内核已有的governor。
整体上,devfreq的软件框架如下所示:
上图中,devfreq框架内部维护了两个链表,一个链表Governor_list是用于管理系统中所有注册进来的governor,还有一个devfreq_list 是用于管理系统中所有的可调频的设备device_freq。
一个拥有opp_table的可调频设备,需要实现自己的调频函数(图中的xxx_devfreq_profile),再通过devfreq提供的接口(devm_devfreq_add_device)来注册进devfreq框架。在注册时,device_freq会指定自己的governor,devfreq框架负责将device_freq和governor进行匹配 。
用户可以实现自己的governor,并通过接口(devfreq_add_governor)注册进devfreq框架。
三 API和用户接口
3.1 device注册接口介绍
device_freq通过接口devm_devfreq_add_device注册进devfreq框架:该接口的定义如下所示:
struct devfreq *devm_devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
const char *governor_name,
void *data);
设备在调用该函数时,device_freq需要传递四个参数:
1. @dev:设备的device结构体
2. @governor_name:调频设备必须指定一个governor,因为没有governor就无法实现调频。device_freq注册时,devfreq框架会根据该字段自动寻找匹配的governor ,将二者关联。
3. @data:该字段用于governor和device_freq之间传递信息,由用户自己定义,框架并不关心这个数据。
4. @profile: 该profile是一个结构体,类似于去银行注册银行卡时填写的信息表,定义如下(讲解见注释):
struct devfreq_dev_profile {
/* 在注册时,设备初始的工作频率,必须 */
unsigned long initial_freq;
/*
* 当governor启动monitor机制时,monitor会每隔一定的时间更新设备的频率,这个位段就指定了该
* 时间间隔。当该值为0时,表示不启用monitor机制。
*/
unsigned int polling_ms;
/* 底层设备的调频函数,设备必须自己实现该函数,并在注册时通过该结构体提供给框架 */
int (*target)(struct device *dev, unsigned long *freq, u32 flags);
/* 该函数目前很少被用到,可以不实现 */
int (*get_dev_status)(struct device *dev,
¦ ¦ struct devfreq_dev_status *stat);
/* 底层设备的获取当前设备频率的函数,设备必须自己实现该函数,并提供给框架 */
int (*get_cur_freq)(struct device *dev, unsigned long *freq);
/* 可选的退出函数,当devfreq设备发生错误时,框架会回调该函数。一般不需要实现。 */
void (*exit)(struct device *dev);
/*
* 设备支持的频点表。如果已经在dts中引用了opp_table,并通过dev_pm_opp_of_add_table
* 关联到opp_table,以下两个字段会由devfreq自动填入。
*/
unsigned long *freq_table;
/* 该设备最多支持的频点数 */
unsigned int max_state;
};
可见,该profile结构体包含了device_freq设备在注册进devfreq框架时需要填写的所有信息,其中必须提供的信息包括:
- 初始频率initial_freq;
- 设备调频函数target
- 设备当前频率的获取函数get_cur_freq
另外,devfreq框架需要知道设备支持的所有频点。因此可以在profile中将freq_table和max_state填写,注册的时候由devfreq框架读取;但是更加规范的方法是在dts中引用一个opp_table,然后在设备注册进devfreq框架前,调用dev_pm_opp_of_add_table将设备和opp_table相关联;这样,devfreq框架会自动读取设备的opp_table,将信息整合,代替freq_table和 max_state字段。
3.2 governor使用接口介绍
3.2.1 governor注册接口
governor的注册接口比较简单:
int devfreq_add_governor(struct devfreq_governor *governor);
这里,重点关注传参类型struct devfreq_governor:
struct devfreq_governor {
struct list_head node;
const char name[DEVFREQ_NAME_LEN];
const unsigned int immutable;
int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
int (*event_handler)(struct devfreq *devfreq,
unsigned int event, void *data);
};
governor结构体由以下几个部分组成:
- 节点node,用于加入全局链表governor_list
- governor的名字,用于在device注册进来时进行匹配
- immutable 标志位,当置为1时,表示使用该governor的设备不可以动态更换其他的governor
- get_target_freq回调函数&