电源管理入门-7 DevFreq

上一小节介绍了cpu的调频,那么其他设备例如ddr、usb、spi,还有很多子系统有自己的r核或者m核例如npu、isp等都需要调频,那必须opp给安排上,然后调频就需要我们这里说的devfreq框架。
1. 整体介绍 1.1 devfreq基础概念 opp:
复杂soc由多个子模块协同工作组成,在运行中并非soc中的所有模块都需要始终保持最高性能。为方便起见,将soc中的子模块分组为域,从而允许某些域以较低的电压和频率运行,而其他域以较高的电压/频率对运行。对于这些设备支持的频率和电压对,我们称之为opp(operating performance point)。对于具有opp功能的非cpu设备,本文称之为opp device,需要通过devfreq进行动态的调频调压。
devfreq:
devfreq:generic dynamic voltage and frequency scaling (dvfs) framework for non-cpu devices。是由三星电子myungjoo ham myungjoo.ham@samsung.com,提交到社区。原理和/deivers/cpufreq 非常近似。但是cpufreq驱动并不允许多个设备来注册,而且也不适合不同的设备具有不同的governor。devfreq则支持多个设备,并且允许每个设备有自己对应的governor。
如下图,devfreq framework是功耗子系统的一部分,与cpufreq,cpuidle,powermanager相互配合协作,达到节省系统功耗的目的。
1.2 devfreq框图 整个devfreq framework中的三大部分组成:
devfreq core:devfreq framework的核心,一方面提供需要调频调压设备及governor的注册方法,通过devfreq_list及governor_list分别管理所有的调频调压设备及注册进系统的governor。另一方面,提供具体调频调压的处理逻辑,通过从governor获取目标频率,提供update_devfreq方法供governor调用,从而实现调频调压。 governor:具体的调频策略,需要devfreq framework提供的接口进行注册。内核中已经支持如下策略: simple_ondemand:按需调整模式;根据系统负载,动态地调整频率、电压,平衡性能和功耗。 performance:性能优先模式,将频率及电压调整到最大。 powersave:功耗优先模式,将频率及电压调整到最小。 userspace:用户指定模式,用户通过提供的文件节点,根据需要设置的频率及电压。 passive:被动模式,使用设备指定方法做频率、电压调整,或跟随父devfreq设备的governor进行调整。 devfreq device driver:需要调频调压的设备驱动,需要通过devfreq framework提供的接口进行注册。会通过opp库提供的dts解析函数解析opp频率、电压对。在调频的时候,根据opp库提供频率、电压调整接口借助clk、regulator框架进行调频调压。在查询当前频率时,通过get_cur_freq查询当前的频率。 可以看到这里devfreq和cpufreq的套路基本一样。
1.3 sysfs用户接口 这里以ddr为例:/sys/devices/platform/dmc0/devfreq/devfreq0目录下面
available_frequencies: 可用的频率列表 available_governors:可用的governor cur_freq:当前频率 governor: 当前governor max_freq:最大频率 min_freq :最小频率 polling_interval:governor调度的时间间隔,单位是ms target_freq:目标频率 trans_stat:状态调整表 代码实现在kernel/drivers/devfreq/devfreq.c中
static struct attribute *devfreq_attrs[] = { &dev_attr_governor.attr, &dev_attr_available_governors.attr, &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); 2. linux 关键数据结构和api实现 2.1 主要数据结构 devfreq数据结构和模块关系图
2.1.1 devfreq_dev_profile devfreq profile结构体,是opp device注册到devfreq framework的数据结构,主要包含opp设备的频率相关信息和相关的回调函数,是devfreq framework和opp device driver的交互接口。
struct devfreq_dev_profile { /*devfreq初始化频率*/ unsigned long initial_freq; /*governor轮询的时间间隔,单位ms,0禁止*/ unsigned int polling_ms; /*devfreq framework设置opp device频率的回掉函数*/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获取opp device当前频率的回掉函数*/int (*get_cur_freq)(struct device *dev, unsigned long *freq); /*devfreq framework退出时对opp device的回掉函数*/void (*exit)(struct device *dev); /*opp device支持的频率表*/ unsigned long *freq_table; /*freq_table表的大小*/ unsigned int max_state;}; 初始化使用:
static struct devfreq_dev_profile xxx_devfreq_dmc_profile = { .polling_ms = 300, .target = xxx_dmcfreq_target, .get_dev_status = xxx_dmcfreq_get_dev_status, .get_cur_freq = xxx_dmcfreq_get_cur_freq,}; 2.1.2 devfreq_governor devfreq governor结构体,是governor注册到devfreq framework的数据结构,主要包含governor的相关属性和具体的函数实现。是devfreq framework和governor交互接口。
struct devfreq_governor { struct list_head node; /*该governor的名称*/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);}; 例如使用simple_ondemand
static struct devfreq_governor devfreq_simple_ondemand = { .name = simple_ondemand, .get_target_freq = devfreq_simple_ondemand_func, .event_handler = devfreq_simple_ondemand_handler,}; 2.1.3 devfreq devfreq设备结构体,这个是devfreq设备的核心数据结构。将上述的opp device driver的devfreq_dev_profile和governor的devfreq_governor连接到一起,并通过设备驱动模型中device类,为user 空间提供接口。
struct devfreq { struct list_head node; struct mutex lock; struct mutex event_lock; /*其class属于devfreq_class,父节点指向使用devfreq的device*/struct device dev; /*opp device注册到devfreq framework的配置信息*/struct devfreq_dev_profile *profile; /*governor注册到devfreq framework的配置信息*/const struct devfreq_governor *governor; /*devfreq的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; /*opp device传递给governor的私有数据*/void *data; /* private data for governors */ ......}; 这个数据结构是生成的,没有初始化值。
2.2 devfreq初始化 三个模块:framework、governor、device相关的初始化,其中device靠后。
2.2.1 devfreq framework初始化 在drivers/devfreq/devfreq.c中,devfreq_init()函数
static int __init devfreq_init(void){ devfreq_class = class_create(this_module, devfreq); //创建devfreq设备类 if (is_err(devfreq_class)) { pr_err(%s: couldn't create class, __file__); return ptr_err(devfreq_class); } //创建工作队列,用于负载监控work调用运行 devfreq_wq = create_freezable_workqueue(devfreq_wq); if (!devfreq_wq) { class_destroy(devfreq_class); pr_err(%s: couldn't create workqueue, __file__); return -enomem; } //加入到subsys_initcall,系统启动时初始化 devfreq_class->dev_groups = devfreq_groups; return 0;}subsys_initcall(devfreq_init); devfreq_groups就是上面说的sysfs用户接口
attribute_groups(devfreq);#define attribute_groups(_name) static const struct attribute_group _name##_group = { .attrs = _name##_attrs, }; 2.2.2 governors 初始化 系统中可支持多个governors,在系统启动时进行初始化,并注册到devfreq framework中, 后续opp device创建devfreq设备,会根据governor名字从已经初始化好的governor 列表中,查找对应的governor实例。
下面以simple_ondemand为例子,看下初始化过程:在drivers/devfreq/governor_simpleondemand.c中
//填充governor的结构体,不同的governor,会有不同的实现。static struct devfreq_governor devfreq_simple_ondemand = { .name = simple_ondemand, .get_target_freq = devfreq_simple_ondemand_func, .event_handler = devfreq_simple_ondemand_handler,};static int __init devfreq_simple_ondemand_init(void){ return devfreq_add_governor(&devfreq_simple_ondemand);}//加入到subsys_initcall,系统启动时初始化。subsys_initcall(devfreq_simple_ondemand_init); 初始化将governor加入到devfreq framework的governor列表中。
devfreq_add_governor->list_add(&governor->node, &devfreq_governor_list);
2.2.3 opp device初始化 这里我们就以ddr为例子 drivers/devfreq/dmc.c中,系统根据dts描述添加对应驱动程序
static const struct of_device_id xxxdmc_devfreq_of_match[] = { { .compatible = xxx-dmc }, { },};module_device_table(of, xxxdmc_devfreq_of_match);static struct platform_driver xxx_dmcfreq_driver = { .probe = xxx_dmcfreq_probe, .driver = { .name = xxx-dmc-freq, .pm = &xxx_dmcfreq_pm, .of_match_table = xxxdmc_devfreq_of_match, },};module_platform_driver(xxx_dmcfreq_driver);xxx_dmcfreq_probe 匹配xxx-dmc会执行扫描函数xxx_dmcfreq_probe()
static int xxx_dmcfreq_probe(struct platform_device *pdev){ //ctx是自定义的一个数据结构,用于存放各种ddr dvfs相关信息 ctx = devm_kzalloc(dev, sizeof(struct xxx_dmcfreq), gfp_kernel); //找到clk信息 struct device *dev = &pdev->dev; ctx->dmc_clk = devm_clk_get(dev, dmc_clk); //负载计数启动 ctx->edev = devfreq_event_get_edev_by_phandle(dev, 0); if (is_err(ctx->edev)) return -eprobe_defer; ret = devfreq_event_enable_edev(ctx->edev); //给dev添加opp信息 if (dev_pm_opp_of_add_table(dev)) { dev_err(dev, invalid operating-points in device tree.); return -einval; } ctx->rate = clk_get_rate(ctx->dmc_clk); opp = devfreq_recommended_opp(dev, &ctx->rate, 0); ctx->rate = dev_pm_opp_get_freq(opp); dev_pm_opp_put(opp); xxx_devfreq_dmc_profile.initial_freq = ctx->rate; ctx->devfreq = devm_devfreq_add_device(dev, &xxx_devfreq_dmc_profile, simple_ondemand, &ctx->ondemand_data); //计算出最大最小值 ctx->devfreq->min_freq = ulong_max; ctx->devfreq->max_freq = 0; max_opps = dev_pm_opp_get_opp_count(dev); for (i = 0, rate = 0; i devfreq->min_freq > rate) ctx->devfreq->min_freq = rate; if (ctx->devfreq->max_freq devfreq->max_freq = rate; } devm_devfreq_register_opp_notifier(dev, ctx->devfreq); ctx->dev = dev; platform_set_drvdata(pdev, ctx); return 0;} pdev的名字是dmc0,对应dts中
dmc_0: dmc0 { compatible = xxx-dmc; devfreq-events = ; operating-points-v2 = ; clocks = ; clock-names = dmc_clk; }; 其他信息都是在这里定义的。
struct xxx_dmcfreq { struct device *dev; struct devfreq *devfreq; struct devfreq_simple_ondemand_data ondemand_data; struct clk *dmc_clk; struct devfreq_event_dev *edev; struct mutex lock; unsigned long rate, target_rate;}; devm_devfreq_add_device()函数会调用devfreq_add_device()进行注册devfreq
devfreq_add_device devfreq_add_device 创建devfreq设备的主要流程如下:
//devfreq device申请内存空间 初始化devfreq device结构体后,注册设备。device_register(&devfreq->dev);//根据传入的governor名字,从governor列表中,获取对应的governor实例。governor = find_devfreq_governor(devfreq->governor_name);//发送devfreq_gov_start到governor,开始管理opp device的频率。err = devfreq->governor->event_handler(devfreq, devfreq_gov_start, null); ##2.3 simple_ondemand调频
devfreq framework是大管家负责监控程序的运行, governor提供管理算法, opp device提供自身的负载状态和频率设置的方法实现。 exynos芯片,simple_ondemend策略调频调压流程图
2.3.1 governor启动监控 初始化的时候在上面2.2.3过程中,会调用devfreq_add_device()会给governor发devfreq_gov_start消息,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; devfreq_monitor_start()开始启动调度程序devfreq_monitor
void devfreq_monitor_start(struct devfreq *devfreq){ init_deferrable_work(&devfreq->work, devfreq_monitor); if (devfreq->profile->polling_ms) queue_delayed_work(devfreq_wq, &devfreq->work, msecs_to_jiffies(devfreq->profile->polling_ms));}export_symbol(devfreq_monitor_start); 2.3.2 monitor轮询监控 devfreq_monitor每隔devfreq->profile->polling_ms时间,会调度监控程序工作。工作函数为update_devfreq()
int update_devfreq(struct devfreq *devfreq){ //获取频率 devfreq->governor->get_target_freq(devfreq, &freq); devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq); freqs.old = cur_freq; freqs.new = freq; devfreq_notify_transition(devfreq, &freqs, devfreq_prechange); //设置频率 devfreq->profile->target(devfreq->dev.parent, &freq, flags); freqs.new = freq; devfreq_notify_transition(devfreq, &freqs, devfreq_postchange); } 这里获取频率和设置频率每次都执行,不比较频率是否相同,在设置处理的时候才比较。
2.3.3 governor计算频率 devfreq->governor->get_target_freq对应函数为:devfreq_simple_ondemand_func()
static int devfreq_simple_ondemand_func(struct devfreq *df, unsigned long *freq){ //通过device回调函数,获取当前状态,然后计算新的频率 err = devfreq_update_stats(df); //新频率的算法,根据阈值和当前负载计算 a = stat->busy_time; a *= stat->current_frequency; b = div_u64(a, stat->total_time); b *= 100; b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); *freq = (unsigned long) b; if (df->min_freq && *freq min_freq) *freq = df->min_freq; if (df->max_freq && *freq > df->max_freq) *freq = df->max_freq;} devfreq_update_stats会执行:df->profile->get_dev_status(df->dev.parent, &df->last_status); 见2.3.4分析
2.3.4 device获取和设置频率 static struct devfreq_dev_profile xxx_devfreq_dmc_profile = { .polling_ms = 200, .target = xxx_dmcfreq_target, .get_dev_status = xxx_dmcfreq_get_dev_status, .get_cur_freq = xxx_dmcfreq_get_cur_freq,}; xxx_dmcfreq_get_dev_status()获取当前device负载信息,根据算法,返回调整频率。
ret = devfreq_event_get_event(dmcfreq->edev, &edata); if (ret current_frequency = dmcfreq->rate; stat->busy_time = edata.load_count; stat->total_time = edata.total_count; 获取运行状态信息,供monitor中devfreq_update_stats()函数使用
update_devfreq中最后会设置频率xxx_dmcfreq_target() xxx_dmcfreq_target()->clk_set_rate()->dmc_set_rate()->smc指令 xxx_ddr在dtsi中定义
xxx_ddr: xxx_ddr { compatible = xxx-ddr; method = smc; fid = ; test_cmd = ; get_channels_cmd = ; set_cmd = ; get_cmd = ; }; 执行这个smc指令后返回值为2,是channel的最大值,用于校验。
注册完了之后,clk会获取rate调用dmc_recalc_rate()函数,发送smc命令0x82000008 0x00000011 0 获取了rate值为4266000 这里利用atf把这个寄存器设置给封装了。
3. atf相关软件标准流程 为什么操作的动作要放在atf里面?
为了安全,进入安全世界才能操作,普通应用app进不去 为了进入aon(always on)一直运行的非ddr区域运行,例如sram 0x82000008 smc可以查询arm的smc手册
可以参考atf中rk的实现,ddr_get_rate()函数 在plat/rockchip/common/rockchip_sip_svc.c中
/* define a runtime service descriptor for fast smc calls */declare_rt_svc(rockchip_sip_svc,oen_sip_start,oen_sip_end,smc_type_fast,null,sip_smc_handler); sip_smc_handler--》rockchip_plat_sip_handler--》ddr_smc_handler
uint32_t ddr_smc_handler(uint64_t arg0, uint64_t arg1, uint64_t id, uint64_t arg2){switch (id) {case dram_set_rate:return ddr_set_rate((uint32_t)arg0);case dram_round_rate:return ddr_round_rate((uint32_t)arg0);case dram_get_rate:return ddr_get_rate();case dram_set_odt_pd:dram_set_odt_pd(arg0, arg1, arg2);break;default:break;}return 0;} 这里对于ddr的调频代码需要放到aon区域,系统中除了ddr还有sram,ddr调频的代码不能放到ddr里面,或者使用硬件dmc实现。
dmc调频 是用软件来升频或者降频,软件是运行在soc的system controller上的,常常是cortex-m cpu,调频的时候dmc不会阻止cpu transfers,dmc自己有buffer,可以继续接收,只是不会发给ddr,这些对cpu是透明的,但如果buffer满了的话,cpu自然就发不了了,调频之后可能需要ddr calibration,我们也都是通过dmc驱动程序来完成的,只是在做这些操作的时候并不会让系统停下来。
后记
学习armv8,rk也就是rockchip是不错的板子选择,还记得以前买过萤火虫的rk板卡,所有软硬件资料都很全,还挺不错的。这里的电源管理也算是驱动,学习驱动还是能有个板子调下,主要区分是32位还是64位,目前的大型soc基本都是armv8的64位,甚至armv9了。

永磁电机和感应电机的区别
工程师应该掌握的电路识别与分析方法
金融时报:半导体芯片引发中美角力
内存条故障导致电脑黑屏的解决方法
随着智能家电市场的快速引爆 智能穿戴设备将再次掀起一场风暴
电源管理入门-7 DevFreq
全频道调频接收机的工作原理解析
小米首次发布智能追踪式无线充电技术
iSuppli发布无线市场关键数据
什么是扩展频谱通信,什么是信息带宽
吉咖智能机器人有限公司总部开业仪式在苏州举行
PCBA快速打样的收费标准
变电站自动化系统的结构与功能
泰凌蓝牙核心规范5.2及5.3主要特性
如何成为一台可随时加速的精密机器?
断路器可以横向安装吗?
“中低压直流配用电系统关键技术及应用”项目启动会在南京召开
电脑屏幕亮度也能用作盗取数据且无需网络连接
铁丝是怎么样制作而成的,它的制作需要什么仪器
解析斯年智驾“重资产”模式前景如何?