当前位置: 首页 > article >正文

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 接口更新设备的频率,该接口实现流程如下图所示:
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 表得来。


http://www.kler.cn/a/387750.html

相关文章:

  • MySQL如何利用索引优化ORDER BY排序语句
  • linux,1.NFS和autofs,2.podman容器,3.http服务和虚拟web主机,4.内网DNS服务搭建
  • 图像处理自动渲染代码
  • 如何平滑切换Containerd数据目录
  • 面经—科大讯飞
  • FPGA学习笔记#4 Vitis HLS 入门的第一个工程
  • flink 内存配置(五):网络缓存调优
  • video素材格式转换--mp4转webm(vue3+Nodejs)
  • 如何运营Github Org
  • Hunyuan-Large:推动AI技术进步的下一代语言模型
  • 刘艳兵-DBA027-在Oracle数据库,通常可以使用如下方法来得到目标SQL的执行计划,那么通过下列哪些方法得到的执行计划有可能是不准确的?
  • 鸿蒙next版开发:ArkTS组件自定义事件拦截详解
  • 腾讯混元3D模型Hunyuan3D-1.0部署与推理优化指南
  • 【PGCCC】Postgresql LWLock 原理
  • 孤岛的总面积(Dfs C#
  • IP系列之scan讨论
  • Java学习篇之JVM 调优
  • Hive 的数据类型
  • 分布式中常见的问题及其解决办法
  • BUG: scheduling while atomic
  • 软考中级 软件设计师 上午考试内容笔记(个人向)Part.2
  • 道品科技智慧农业中的自动气象检测站
  • vue3+ts+element-ui实现的可编辑table表格组件 插入单行多行 组件代码可直接使用
  • 当AI遇上时尚:未来的衣橱会由机器人来打理吗?
  • 133.鸿蒙基础01
  • 在IntelliJ IDEA中创建带子模块的SpringBoot工程