linux devfreq 模块
文章目录
- 一、简介
- 二、关键数据结构
- 2.1 devfreq_dev_profile 结构体
- 2.2 devfreq_governor 结构体
- 2.3 devfreq 设备结构体
- 三、关键接口和工作流程
- 3.1 devfreq framework 初始化
- 3.2 添加 devfreq 设备
- 3.3 governor 初始化
- 3.4 频率监控和调整过程
- governor_simpleondemand
- userspace\powersave\performance
- 四、通过修改 opp 来控制 devfreq 调频
- 4.1 注册 devfreq_cooling 设备
- 4.2 温控子系统修改 opp 状态
- 4.3 devfreq 获取 opp 状态
一、简介
devfreq 在 linux 内核中主要用于动态调节设备频率,以优化功耗和性能。提供了管理设备(如 GPU\摄像头\ddr)频率的接口,以便系统能够根据实际需求动态地改变设备的工作频率。
devfreq 可能在温控系统中提供 cooling dev 的调频接口,也可能在功耗管理模块中提供功耗管理的调频接口。
二、关键数据结构
devfreq 子系统中主要的结构体有描述设备接口的结构体 devfreq_dev_profile、描述设备调频策略的结构体 devfreq_governor、以及核心结构体 devfreq。
2.1 devfreq_dev_profile 结构体
devfreq_dev_profile
结构体定义了设备频率管理的相关信息和操作接口。
struct devfreq_dev_profile {
unsigned long initial_freq; // 初始化频率,默认工作频率
unsigned int polling_ms; // 以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); // 返回当前频率
void (*exit)(struct device *dev); // 设备退出时清理函数回调
unsigned long *freq_table; // 设备opp支持的频率表
unsigned int max_state; // freq_table表大小
};
2.2 devfreq_governor 结构体
devfreq_governor
结构体定义了调频策略的相关接口和信息。
struct devfreq_governor {
struct list_head node;
const char name[DEVFREQ_NAME_LEN];
const unsigned int immutable; // 为1时表示该策略不可改变(不能切调频策略)
// 调频策略算法,根据当前状态(温度、负载等)结合策略算法,返回设备应该设置的频率
int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
// 处理特定事件,如start\stop\suspend\resume等事件
int (*event_handler)(struct devfreq *devfreq, unsigned int event, void *data);
};
2.3 devfreq 设备结构体
devfreq 结构体用于描述一个可以调频的设备的所有信息,包括设备调频接口、调频策略、状态等信息。
struct devfreq {
struct list_head node;
struct mutex lock;
struct device dev;
struct devfreq_dev_profile *profile; // 设备调整频率的相关接口和参数
const struct devfreq_governor *governor; // 设备调频策略
char governor_name[DEVFREQ_NAME_LEN];
struct notifier_block nb; // 设备状态变化相关的通知nb
struct delayed_work work; // devfreq 监控的work
unsigned long previous_freq; // 上一次设置的频率
struct devfreq_dev_status last_status; // 设备最后的状态
void *data; /* private data for governors */
unsigned long min_freq; // 设备支持频率的上下限
unsigned long max_freq;
unsigned long scaling_min_freq; // opp请求的最小和最大频率
unsigned long scaling_max_freq;
bool stop_polling; // 停止轮询的标志位
unsigned long suspend_freq;
unsigned long resume_freq; // 设备suspend和resume时需要设置的频率
atomic_t suspend_count; // suspend计数器
/* 记录频率转换的一些信息 */
unsigned int total_trans; // 转换次数
unsigned int *trans_table; // 指向转换表的信息
unsigned long *time_in_state; // 每种频率状态的持续时间
unsigned long last_stat_updated; // 最后一次状态更新的时间戳
struct srcu_notifier_head transition_notifier_list;
};
三、关键接口和工作流程
3.1 devfreq framework 初始化
devfreq framework 初始化非常简单:
● 创建设备类;
● 创建工作队列,后续用于频率监控任务调用;
static int __init devfreq_init(void)
{
devfreq_class = class_create(THIS_MODULE, "devfreq");
devfreq_wq = create_freezable_workqueue("devfreq_wq");
devfreq_class->dev_groups = devfreq_groups;
return 0;
}
subsys_initcall(devfreq_init);
3.2 添加 devfreq 设备
使用 devm_devfreq_add_device
或者 devfreq_add_device
接口添加 devfreq 设备。
devm_ 前缀的接口区别就是使用了设备管理机制,当设备被创建时,内核会自动管理资源的分配和释放;当设备不再使用(例如驱动被卸载时),内核会自动调用 devm_devfreq_dev_release 接口来清理相关资源。
函数声明如下:
struct devfreq *devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
const char *governor_name,
void *data)
- struct devfreq_dev_profile *profile,入参 profile 是设备操作函数和相关信息的结构体指针;
- const char *governor_name,入参 governor_name 表示默认配置的 governor;
- void *data,data 是传递给 governor 的私有数据。
具体实现如下:
struct devfreq *devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
const char *governor_name,
void *data)
{
// 检查是否已经注册过devfreq设备
mutex_lock(&devfreq_list_lock);
devfreq = find_device_devfreq(dev);
mutex_unlock(&devfreq_list_lock);
if (!IS_ERR(devfreq)) {
dev_err(dev, "%s: Unable to create devfreq for the device.\n",
__func__);
err = -EINVAL;
goto err_out;
}
devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL);
// devfreq设备相关信息的初始化
mutex_init(&devfreq->lock);
mutex_lock(&devfreq->lock);
devfreq->dev.parent = dev;
devfreq->dev.class = devfreq_class;
devfreq->dev.release = devfreq_dev_release;
INIT_LIST_HEAD(&devfreq->node);
devfreq->profile = profile;
strncpy(devfreq->governor_name, governor_name, DEVFREQ_NAME_LEN);
devfreq->previous_freq = profile->initial_freq;
devfreq->last_status.current_frequency = profile->initial_freq;
devfreq->data = data;
devfreq->nb.notifier_call = devfreq_notifier_call;
// 如果没有创建freq table,从opp读取相关信息创建profile的freq table
if (!devfreq->profile->max_state && !devfreq->profile->freq_table) {
mutex_unlock(&devfreq->lock);
err = set_freq_table(devfreq);
if (err < 0)
goto err_dev;
mutex_lock(&devfreq->lock);
}
// 设置opp支持配置的频率范围和devfreq支持的频率范围
// 受温控或者功耗模块配置影响,opp支持配置的频率范围会改变
// 最小频率配置
devfreq->scaling_min_freq = find_available_min_freq(devfreq);
devfreq->min_freq = devfreq->scaling_min_freq;
// 最大频率配置
devfreq->scaling_max_freq = find_available_max_freq(devfreq);
devfreq->max_freq = devfreq->scaling_max_freq;
// suspend 频率和计数配置
devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev);
atomic_set(&devfreq->suspend_count, 0);
dev_set_name(&devfreq->dev, "%s", dev_name(dev));
err = device_register(&devfreq->dev);
// 频率转换表资源申请
devfreq->trans_table = devm_kzalloc(&devfreq->dev,
array3_size(sizeof(unsigned int),
devfreq->profile->max_state,
devfreq->profile->max_state),
GFP_KERNEL);
devfreq->time_in_state = devm_kcalloc(&devfreq->dev,
devfreq->profile->max_state,
sizeof(unsigned long),
GFP_KERNEL);
devfreq->last_stat_updated = jiffies;
// 初始化频率状态变化的notifier
srcu_init_notifier_head(&devfreq->transition_notifier_list);
mutex_unlock(&devfreq->lock);
mutex_lock(&devfreq_list_lock);
// 注册设备的governor,并通知governor start event
governor = try_then_request_governor(devfreq->governor_name);
devfreq->governor = governor;
err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
NULL);
list_add(&devfreq->node, &devfreq_list);
mutex_unlock(&devfreq_list_lock);
return devfreq;
}
EXPORT_SYMBOL(devfreq_add_device);
3.3 governor 初始化
系统支持的 governor 在启动(subsys_initcall)时调用 devfreq_add_governor 接口注册 governor 到 devfreq framework 中。
以 userspace governor 为例:
static struct devfreq_governor devfreq_userspace = {
.name = "userspace",
.get_target_freq = devfreq_userspace_func,
.event_handler = devfreq_userspace_handler,
};
static int __init devfreq_userspace_init(void)
{
return devfreq_add_governor(&devfreq_userspace);
}
subsys_initcall(devfreq_userspace_init);
static void __exit devfreq_userspace_exit(void)
{
int ret;
ret = devfreq_remove_governor(&devfreq_userspace);
if (ret)
pr_err("%s: failed remove governor %d\n", __func__, ret);
return;
}
module_exit(devfreq_userspace_exit);
MODULE_LICENSE("GPL");
devfreq_add_governor 接口实现如下,实际操作就是:
● 将 governor 添加到 list 链表中;
● 遍历已经注册的 devfreq 设备链表,如果 governor->name 与 devfreq->governor_name 相等,则将 governor 注册为 devfreq 的 governor;
int devfreq_add_governor(struct devfreq_governor *governor)
{
mutex_lock(&devfreq_list_lock);
// 检查是否已经注册
g = find_devfreq_governor(governor->name);
// 添加到governor list中
list_add(&governor->node, &devfreq_governor_list);
// 遍历已经注册的devfreq设备列表,将governor注册到对应的devfreq上
list_for_each_entry(devfreq, &devfreq_list, node) {
int ret = 0;
struct device *dev = devfreq->dev.parent;
if (!strncmp(devfreq->governor_name, governor->name,
DEVFREQ_NAME_LEN)) {
devfreq->governor = governor;
ret = devfreq->governor->event_handler(devfreq,
DEVFREQ_GOV_START, NULL);
}
}
err_out:
mutex_unlock(&devfreq_list_lock);
return err;
}
EXPORT_SYMBOL(devfreq_add_governor);
3.4 频率监控和调整过程
devfreq 系统使用 update_devfreq 接口更新设备的频率,该接口实现流程如下图所示:
governor_simpleondemand
simpleondemand 算法根据设备当前的负载调整工作频率。
在注册 devfreq 设备或者向 devfreq framework 中注册 governor 时,会像 governor 发送 DEVFREQ_GOV_START
事件,governor 在处理 start 事件时调用 devfreq_monitor_start
接口启动通过 delay work 实现的 devfreq 监控和调整模拟器。
userspace\powersave\performance
这三种 governor 比较简单,不需要依赖任务来更新频率:
- userspace:在用户修改频率时,调用 update_devfreq 接口更新频率,get_target_freq() 接口返回的是用户定义的频率;
- performance:在 start event 处理时,调用 update_devfreq 接口更新频率,get_target_freq() 接口返回的是最大值;
- powersave:在 start event 处理时,调用 update_devfreq 接口更新频率,get_target_freq() 接口返回的是最小值;
四、通过修改 opp 来控制 devfreq 调频
linux温控子系统参考 :《linux thermal 温控子系统》
linux opp模块参考 :《linux opp模块》
在 linux 中温控子系统或者功耗子系统中,都是通过修改设备的 opp 属性来控制设备的频率的。
以温控系统中的 devfreq_cooling 设备为例,代码在 drivers\thermal\devfreq_cooling.c
中。
4.1 注册 devfreq_cooling 设备
在 devfreq device 初始化的时候,调用 of_devfreq_cooling_register
接口,将 devfreq 设备注册为温控子系统中的 devfreq_cooling 设备。
同时,调用 devfreq_register_opp_notifier
接口将 update_devfreq 注册为 notifier_block 回调,该回调最终注册到 opp_table 中,当 opp 状态发生变化时,会调用 update_devfreq 接口更新设备频率。
4.2 温控子系统修改 opp 状态
opp 中使用 dev_pm_opp 结构体表示 opp 项,结构体中的 available 表示 opp 是否 enable。
struct dev_pm_opp {
……
bool available; // enable/disable
……
}
温控子系统中调频接口为 devfreq_cooling_set_cur_state
,该接口调用 partition_enable_opps
,将大于输入的 state 的 opp 状态全部设置为 disable(opp_table 中的 opp 按照频率从低到高的顺序排列),实现将大于某 freq 的 opp 状态设置为 disable:
static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
ret = partition_enable_opps(dfc, state);
dfc->cooling_state = state;
return 0;
}
static int partition_enable_opps(struct devfreq_cooling_device *dfc,
unsigned long cdev_state)
{
int i;
struct device *dev = dfc->devfreq->dev.parent;
for (i = 0; i < dfc->freq_table_size; i++) {
struct dev_pm_opp *opp;
int ret = 0;
unsigned int freq = dfc->freq_table[i];
bool want_enable = i >= cdev_state ? true : false;
opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable);
if (PTR_ERR(opp) == -ERANGE)
continue;
else if (IS_ERR(opp))
return PTR_ERR(opp);
dev_pm_opp_put(opp);
if (want_enable)
ret = dev_pm_opp_enable(dev, freq);
else
ret = dev_pm_opp_disable(dev, freq);
if (ret)
return ret;
}
return 0;
}
opp 模块中调用 _opp_set_availability
接口配置 opp available state,更新状态后调用 notifier 回调,也就是 devfreq 设备的 update_devfreq 接口。
static int _opp_set_availability(struct device *dev, unsigned long freq,
bool availability_req)
{
opp->available = availability_req;
/* Notify the change of the OPP availability */
if (availability_req)
blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_ENABLE,
opp);
else
blocking_notifier_call_chain(&opp_table->head,
OPP_EVENT_DISABLE, opp);
return r;
}
4.3 devfreq 获取 opp 状态
如在 3.4 中描述的 update_devfreq 流程,该接口先根据 governor 算法计算出 target_freq 目标频率,调用 devfreq device 的 target 接口设置频率。
在 devfreq device 设置频率是,需要调用 devfreq_recommended_opp
,根据 target_freq 从 opp 表中查找最合适的频率,然后将频率设置为 opp 表返回的频率。
struct dev_pm_opp *devfreq_recommended_opp(struct device *dev,
unsigned long *freq,
u32 flags)
{
struct dev_pm_opp *opp;
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) {
/* The freq is an upper bound. opp should be lower */
opp = dev_pm_opp_find_freq_floor(dev, freq);
/* If not available, use the closest opp */
if (opp == ERR_PTR(-ERANGE))
opp = dev_pm_opp_find_freq_ceil(dev, freq);
} else {
/* The freq is an lower bound. opp should be higher */
opp = dev_pm_opp_find_freq_ceil(dev, freq);
/* If not available, use the closest opp */
if (opp == ERR_PTR(-ERANGE))
opp = dev_pm_opp_find_freq_floor(dev, freq);
}
return opp;
}
EXPORT_SYMBOL(devfreq_recommended_opp);
也就是 devfreq governor 算法计算的 target_freq 只是推荐频率,最终设置的频率可以通过查找 opp 表得来。