Bootstrap

Linux 内核学习(4) --- devfreq 动态调频框架

Linux devfreq 简介

现在的 Soc 由众多的子模块构成,比如CNNDSPISPCPU等,在不同的场景下,并非所有的模块都要保持最高的性能,因此,SoC 设计的时候会划分一些电压域,这些电压域内的模块,可以根据具体需要调整电压和频率,从而达到既能实现功能,又能降低功耗的目的

不过频率电压并不是随意搭配的,一般情况下,高频对应着高压,低频对应着低压,这是由于晶体管的电器特性决定的

Linux 内核用 OPP(Operation Performance Point) 对这些设备支持的频率和电压进行描述和管理,CPUDVFS 也就是 cpufreq 也是基于 OPP 实现的,但是仅仅支持 CPU 设备的调频和调压,这里主要介绍的是设备的 DVFS,也是基于 OPP 实现的,Linux 内核实现了一个 devfreq framework 用于实现和管理 devicedvfs,用于支持非CPU设备的调频,调压,并且设备可以匹配自己的 governor 策略

devFreq framework 规范了设备调频调压的过程,也标准化了用户空间的控制接口,通过设备的需求,利用 governor 策略控制频率,进而根据 opp table 选择对应的电压
devfreq_framework

核心数据结构

devfreq_dev_profile 结构体

devfreq_dev_profile 结构体, OPP device 注册到 devfreq framework 的数据结构,主要包含 OPP 设备的频率信息和相关的回调函数,是 devfreq frameworkOPP device 的交互接口,将 OPP device driverdevfreq 的使用,简化为对 devfreq profile 结构体的填充

// drivers/devfreq/devfreq.c 
// include/linux/devfreq.h
struct devfreq_dev_profile {
	// devfreq 注册的初始化频率
	unsigned long initial_freq;
	// governor polling 的时间间隔,单位 ms
	unsigned int polling_ms;

	// devfreq framework用于设定 OPP device frequency的回调函数
	int (*target)(struct device *dev, unsigned long *freq, u32 flags);
	// devfreq framework 用于获取 OPP device 负载状态的回调函数
	int (*get_dev_status)(struct device *dev, struct devfreq_dev_status *stat);
	// devfreq framework 用于获取当前频率的回调函数
	int (*get_cur_freq)(struct device *dev, unsigned long *freq);

	// 当 devfreq 设备从 devfreq framework 移除的时候调用
	// 调用时机是 error 或者 devfreq_remove_device() call
	void (*exit)(struct device *dev);

	unsigned long *freq_table;
	unsigned int max_state;
};
devfreq_governor 结构体

devfreq_governorgovernor 注册到devfreq framework 的数据结构,主要包含 governor 的相关属性和具体的函数实现,是devfreq frameworkgovernor 的交互接口

struct devfreq_governor {
    // 将 devfreq_governor 作为链表节点进行管理
	struct list_head node;
    // governor name
	const char name[DEVFREQ_NAME_LEN];
    // 是否可以切换为其他 governor 1:不能切换
	const unsigned int immutable;

    // governor 注册到 devfreq framework 的算法实现函数,返回调整后的频率
	int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
	// governor 注册到 devfreq framework 的 event 处理函数,处理 start,stop,suspend,resume 等 event
    int (*event_handler)(struct devfreq *devfreq,
				unsigned int event, void *data);
};

其中的 get_target_freq 一般会调用 devfreq_dev_profile.get_dev_status() 获取当前 OPP device 的负载情况 (load = busy_time / total_time)

devfreq 结构体

devfreq 结构体是 devfreq device 的核心结构,每个注册的设备都会新建一个 devfreq 结构体,
作用是将上述 OPP devicedevfreq_dev_profilegovernordevfreq_governor 连接到一起,
并通过设备模型的 device 类,为user空间提供接口

struct devfreq {
	struct list_head node;
	struct mutex lock;
    // device 属于 devfreq_class, 父节点就是使用 devfreq 的 device 比如 GPU,DDR等
	struct device dev;
    // OPP device 注册到 devfreq framework 的配置信息
	struct devfreq_dev_profile *profile;
    // governor注册到devfreq framework的配置信息
	const struct devfreq_governor *governor;
	char governor_name[DEVFREQ_NAME_LEN];
	struct notifier_block nb;

    // 用于监控负载情况的 delayed work
	struct delayed_work 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;
	bool stop_polling;

	/* information for device frequency transition */
    // 记录 devfreq 中 frequency 的转移信息
	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;
};

工作流程

devFreq framework 初始化

逻辑非常简单,主要完成下面的任务:

  • 创建 devfreq_class 设备类
  • 创建 delayed work,是用于监控负载的工作队列
  • 加入到 subsys_initcall,系统启动时进行初始化
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);
governor 初始化

系统中可以支持多个 governor,在系统启动时就会进行初始化,并注册到 devFreq framework 中,后续的 OPP device 创建 devFreq 设备,
会根据 governor name 从已经初始化的 governor 列表中,查找对应的 governor 实例

// driver/devfreq 目录下 governor_performance.c  
// driver/devfreq 目录下 governor_simpleondemand.c

static struct devfreq_governor devfreq_performance = {
	.name = "performance",
	.get_target_freq = devfreq_performance_func,
	.event_handler = devfreq_performance_handler,
};

static int __init devfreq_performance_init(void)
{
	return devfreq_add_governor(&devfreq_performance);
}
subsys_initcall(devfreq_performance_init);

主要流程:

  1. 填充 devfreq_governor 结构体,不同类型的结构体,对 get_target_freqevent_handler 会有不同的实现
  2. 调用 devfreq_add_governor 加入到 devfreq frameworkgovernor 列表中
  3. 加入到 subsys_initcall,系统启动时进行初始化

目前系统支持下面几种 governor

  • Simple_ondemand: 按需调整模式,根据负载动态频率,平衡性能和功耗
  • Performance: 性能优先模式,调整到最大频率
  • Powersave: 功耗优先模式,调整到最小频率
  • Userspace: 用户指定模式,根据用于 sysfs 节点写入进行调整
  • Passive: 被动模式,使用设备指定方法做调整或者跟随father devfreq 设备的governor
devfreq Device 注册

这里以开源的 mali gpu midgard 内核为例

  1. 配置正常的OPP table,确定正常的 OPP 电压和频率
  2. 获取 OPP device 的频率信息,完善正确的回调函数,最后填充 devfreq profile 结构体
  3. 调用 devfreq_add_device 函数,将 devfreq profile 结构,governor name 添加到 devfreq 框架
int kbase_devfreq_init(struct kbase_device *kbdev)
{
	struct devfreq_dev_profile *dp;
	dp->initial_freq = kbdev->current_freqs[0];
	dp->polling_ms = 100;
	dp->target = kbase_devfreq_target;
	dp->get_dev_status = kbase_devfreq_status;
	dp->get_cur_freq = kbase_devfreq_cur_freq;
	dp->exit = kbase_devfreq_exit;
	
	kbdev->devfreq = devfreq_add_device(kbdev->dev, dp, "simple_ondemand", NULL);
}
动态变频的实现

devfreq framework 负责监控程序的运行,governor 提供算法,OPP device 提供自身负载状态和频率设置放的实现,系统中有不同的 governor,不同的 governor 有不同的管理算法

  1. devfreq_add_device 时发送 DEVFEQ_GOV_START 事件
struct devfreq *devfreq_add_device(struct device *dev,
				   struct devfreq_dev_profile *profile,
				   const char *governor_name,
				   void *data) {
	......
	devfreq->governor = governor;
	err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
				NULL);

	goto err_init;
}
  1. governorevent_handler 收到 DEVFREQ_GOV_START事件,调度工作队列,运行负载监控程序

Note: 这里以 simple_ondemand governor 为例

static int devfreq_simple_ondemand_handler(struct devfreq *devfreq,
				unsigned int event, void *data) {
	switch (event) {
	case DEVFREQ_GOV_START:
		devfreq_monitor_start(devfreq);
		break;
	......
}
  1. 负载监控程序
  • 调用 governorget_target_freq 方法,获取下一次的调频目标值
  • 调用 OPP device 注册到 devfreq_frameworktarget 函数,设置新的频率信息
  • 调度延迟工作队列,延迟 OPP device 设置的轮询间隔,再次运行
static void devfreq_monitor(struct work_struct *work)
{
	int err;
	struct devfreq *devfreq = container_of(work,
					struct devfreq, work.work);

	mutex_lock(&devfreq->lock);
	// monitor 程序的核心函数
	err = update_devfreq(devfreq);

    /* 以 poolling_ms 为周期进行周期监控 */
	queue_delayed_work(devfreq_wq, &devfreq->work,
				msecs_to_jiffies(devfreq->profile->polling_ms));
	mutex_unlock(&devfreq->lock);
}

int update_devfreq(struct devfreq *devfreq)
{
    /*获取要调频到的结果频率*/
    devfreq->governor->get_target_freq(devfreq, &freq);

    /*在调频前后都有通知发出来*/
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
    /*调用 OPP devices的 target 函数设置目标频率*/
    devfreq->profile->target(devfreq->dev.parent, &freq, flags);
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
}
device_unregister 流程

device_unregister 注销过程中会调用 devfreq_dev_release 函数,完成下面的事务:

  1. 发送 DEVFREQ_GOV_STOP eventgovernor停止运行
  2. 回调 OPP device 注册到 devfreq framework 的exit函数
  3. 释放 devfreq device 申请的资源
static void devfreq_dev_release(struct device *dev)
{
	struct devfreq *devfreq = to_devfreq(dev);

	......
	if (devfreq->governor)
		devfreq->governor->event_handler(devfreq,
						 DEVFREQ_GOV_STOP, NULL);

用户空间节点

devfreq framework 初始化时,会创建下面的节点:

static struct attribute *devfreq_attrs[] = {
	&dev_attr_governor.attr, 
	&dev_attr_available_governors.attr, // 可用的 governor
	&dev_attr_cur_freq.attr, //当前频率
	&dev_attr_available_frequencies.attr,// 可用频率列表
	&dev_attr_target_freq.attr, // 目标频率
	&dev_attr_polling_interval.attr, // 调度间隔
	&dev_attr_min_freq.attr, // 最小频率
	&dev_attr_max_freq.attr, // 最大频率
	&dev_attr_trans_stat.attr, // 状态调整记录表
	NULL,
};
ATTRIBUTE_GROUPS(devfreq);

available_frequencies: 可用的频率列表
available_governors:可用的governor
cur_freq:当前频率
governor: 当前governor
max_freq:最大频率
min_freq :最小频率
polling_interval:governor调度的时间间隔,单位是ms.
target_freq:目标频率
trans_stat:状态调整表记录

参考文章

https://mp.weixin.qq.com/s/TI3ryUewRgt9LFKr-tDkJQ
https://www.cnblogs.com/hellokitty2/p/13061707.html

;