经过四天的不断渗透,终于把linux关于动态电源管理的内核相关代码了解了。
十分幸运的,这个项目启用了核心芯片是imx31,正因为他提供了电源动态管理的功能,所以才能在使用中接触到相关的知识。否则如果选择了不具备这个功能的芯片,那么接触到动态电源管理的知识的机会就很少了。
同样抱着学习的态度,跟他一起解析这个部分相关的内核代码和用户层守护程序。
在整个过程中,也是有过很多错误的理解,但是经过与他的不断沟通和讨论,最后得以能深入理解这个部分的概念。
还是比较喜欢这种开发方法,因为一个人的观点永远无法涵盖到所有方面,而且经过沟通和讨论,不但可以减少相同部分的重复阅读,也可以彼此提问,为对方提供线索或者纠正错误。这也是这个部分的理解速度超过我的预期的原因。
这其中另外一个比较运气的地方是,我们获得了基于imx31的linux内核,关于电源管理芯片mc13783的驱动,关于dpm驱动相关的设备无关层的代码,dpmd的应用层守护进程的代码。最后还有imx31的手册。这些都是最后能够理解的必要条件。
一.概念
那么言归正传,首先是对于几个概念的理解。
DPTC:Dynamic Process Temperature Compensation
动态处理温度补偿。从imx31的相关代码来看,这部分是由硬件自动处理。通过检测温度,进而调压调频。这是一个独立的部分,虽然在代码中与dvfs交叉在一起,但是我们不用自己处理其中的参数。在DPTC的中断处理函数中做了相应的处理。这个部分由于没有涉及到我们的工作,所以对代码没有深入解析。
2. DVFS:Dynamic Voltage Frequency Scaling
动态电压频率扩缩。在imx31中,支持硬件的反弹式自动处理和软件的预知处理。这两种方式只可以选择其一。其中硬件的反弹式处理,是基于中断的,也就是说,用户是不干预dvfs的操作的。所以如果选择这种方式的话,对用户来说,是不需要任何工作的。
软件的预知处理。在这种方式下,内核会将极端时间内的15次负载采样返回给用户空间,此时用户空间的程序需要通过一定的算法,给cpu选择一个合适的频率。这里值得注意的是,这15次负载采样,是完全基于硬件寄存器(load track),通过dma上传到用户空间的buf中的,那么说明这是一个极端时间内的负载采样值,在用户层的程序需要关注的是这一段时间内的采样规律,进而动态的调整cpu的频率。
Dptc 和 dvfs 在用户空间的接口是 /dev/dptc_dvfs
3.DPM
动态电源管理。这个部分的代码在2.6的内核中并没有发现,而是通过网络搜索获得的。
在dpm中,有几种术语。
opt,operation point。操作点。在整个dpm中,最后的操作端点,对他的操作就和直接导致cpu频率的改变。Opt中包含了硬件相关的参数。所有的opt构成一个dpm_opts的链表。
class。类。一个class可以包含若干个opt。但只有一个opt可以使被selected的。所有的class构成一个dpm_classes的链表。
policy。策略。一个策略可以包含若干个class。所有的policy构成一个dpm_policies的链表。
在用户层设置策略时,最终将会路由到对应的opt。在opt层完成最终的处理。
Dpm的用户层守护进程为dpmd。其中也有关于负载的判断,但负载的来源位于/proc/stat。也就是说,这是一个稍长时间的负载值。在被解析的dpmd中,可以对cpu的负载划定范围,在某个范围之内选用某个策略,在范围之外选择其他策略。也可指定在某些程序运行时执行某个策略。
其实,在用户层的dpmd中的算法并不限于此,比如可以基于电源类型,电池电量等等的相关条件来进行策略的调整。而且对dpm的策略设定并不限于两个。
Dpm 对用户层的接口是/proc/driver/dpm/cmd
由此可见,dpm关注的是相对长时间内的cpu负载状况,他的算法的输入来源多基于整个设备的状态。而dvfs的算法来源则是几十个机器周期内的负载状态。
我在解析的初期的一个错误的想法是,把dpm和dvfs当成一个功能来看待,试图把他们作为一个整体来解析。混淆了两者在功能上和关注点上的概念和差别。
二.细节
以下的结论完全基于imx31提供的驱动代码,所以并不是可以通用到其他平台上。
Dvfs和dpm在概念是有差别的。但又不是各自孤立的部分,两者之间也存在着一定的联系。下面的图可以或多或少解释一下。
从上到下,是dpm和dvfs的路由关系。最终我们看到他们共同调用了一个函数:dvfs_set_state。这个函数依赖于imx31提供的dvfs table。在table中定义了imx31可以提供的几个档位的频率相关的pmic参数和cpu CCM寄存器参数。从imx31提供的table来看,目前支持4个频率档。Table中的每个值都需要硬件手册的严格对应,所以自己配置table是比较困难的事。
dvfs_set_state 的参数就是4个频率档的索引,范围为0-3,依次对应532M,266M,133M,133M。至于为什么最后两个档频率相同,没有深入解析,但是肯定会有不同。
纵然在概念里讲述了,两者的不同之处,但在细节上还有其他的功能上的不同。当然这是属于设备相关层的,也就是imx31提供的扩展,也就是说,脱离了imx31以下的不一定成立。
在Mxc_dpm_set_opt中,提供了对电源状态的控制,也就是说,通过dpm可以是cpu进入不同的状态:wait,stop,dsm,run。
这个功能是dpm另外一个不同。所以通过设计dpm的结构,可以完成系统状态的切换,取代一部分电源管理的功能。
三.实现
1.dvfs。dvfs的实现相对dpm比较简单,因为在内核中已经向用户层提供了file_ops的接口/dev/dvfs_dptc。这里以dvfs的预知方法为例。在编译时,选择了dvfs的dma支持以后,/dev/dvfs_dptc的操作就多了read。在用户层的程序中,首先通过ioctl start dvfs,
接着set dvfs mode = predict mode(dvfs默认是硬件反弹模式,这里把默认的改为预知模式)。进而从read中读取负载状况,做算法处理,最后将调频的结果通过ioctl写回dvfs。这里的算法可以参考从网上下载的一篇文章,里面介绍了几种常用的dvs算法。
在这里,刘兴宝提到一个性能问题:这个用户态的程序因为不断读取内核数据,那么自身会占用多少资源呢?至少现在没法解答,期待最终试验结果。
dpm。dpm的实现分两个部分,第一是内核的驱动部分,第二是用户层的dpmd。
A. 内核部分。对这个部分的代码解析得知,B. 这部分的设备C. 无关代码并不D. 完全与imx31相匹配。所以要做一定的修改。而E. 且例程中的代码,F. 是基于另外一个cpu在书写的,G. 因此也不H. 知道到底什么是标I. 准的代码。但目标J. 是把两个部分连接起来,K. 那么或者修改这部分从网上的来代码,L. 要么修改imx31提供的代码。具体怎么改,M. 就随意了,N. 可是要注意其中的关键点:struct dpm_md dpm_md。
设备无关层和设备相关层就是通过这个点来做连接的。这个结构包含几个函数指针,其中必须的是init_opt,set_opt,get_opt。init_opt负责初始化硬件相关的参数dpm_md_pp_t pp到结构struct dpm_md_opt md_opt;set_opt负责将设备无关层传来的策略转化成硬件相关的操作;get_opt负责将硬件相关的信息转换成当前的操作点的信息并上传到设备无关层。
而这之中,重要的是struct dpm_md_opt的构造。他必须满足对硬件操作的要求。
总结:要修改的部分包括init_opt,set_opt,get_opt的参数,struct dpm_md_opt的构造。这样用户层对策略的修改就成功的转化为内核层对硬件信息的操作。
O. 用户层守护进程。由于在内核的设备P. 无关层中,Q. 只是对应用层通过proc提供了操作接口,R. 所以即使配置并编译了这个部分,S. 在内核中还是没有任何关于dpm的信息。在用户态必须通过enable(init),T. create_opt,U. create_class,V. create_policy,W. 来创建相关的dpm信息。
所以这个初始化的创建过程将是dpmd必须的一个部分。个人想法,可以通过一个配置文件来配置dpm中的opt,class,和policy的组织结构。
Dpmd的另外一个部分就是关于策略的算法了。以下是我所想到的参考点。
1).cpu 负载。可以将cpu负载分成几个段。在每个段中,就选择对应的策略方式。
2)。当前运行程序。如果当前系统中运行了,指定的程序,就选择对应的策略方式。
3)。电源状态。如果当前系统使用的是AC电源,那么可以适当提高功耗,提高加权。
4)。电池电量。同cpu负载一样,将电量分为几个段。在每个段中,就选择对应的策略方式。
5)。用户加权。对以上或者更多的参考信息作加权处理,最终获得一个合适的策略方式。
这样每隔一段时间(比如2s)做一次策略调整,就可以达到动态调整的目标了。
这些信息,也需要作为配置文件,传入dpmd中。
四.总结
这样每天都在进步的充实的生活很美好。现在已经写了几篇总结了,感觉总算有了回头查阅曾经工作的指示灯了。
阅读linux内核的工作总是开头很辛苦,因为你并不知道你在看的是一个什么东西,所以在开始的阶段,要明白的不是其中具体的实现细节,而是你在看的是一个什么东西,要从概念上明白他的功能。当然这些不可能一下就可以仔仔细细的分辨开来,所有的认知都是在一步一步的加深。
还有,linux的内核源码总是很纷乱复杂,充满了各种指针,要跟踪一个指针往往费了半天时间,可当你找到源头时,却忘了你要找他的原因,这就是舍本逐末了。这些函数和结构的名字总是很相似,最后就晕了,分辨不清了。所以,一个办法是用笔记下来,记下这其中错综复杂的关系。这样,在我们在树木的枝杈之间穿梭之后,回到森林的上空,会有一条明确的痕迹告诉你这个部分的基本原理。否则,经常的当你再从另一个枝杈返回时,就忘记了,刚才走过的路。
Dvfs和dpm是一个比较针对的东西。因此linux 的内核中没有包含相关的驱动代码。而有机会接触到这个部分,就算是运气了。
回头想想,这个部分的底层代码都是imx31的freescale提供的,如果个人要开发一个针对某个cpu的dvfs驱动将是一个复杂的工程,他要求这个工程师具有linux驱动开发能力,电源芯片驱动开发,cpu的CCM的熟练操作,对这个硬件的电源芯片和cpu自身提供的dvfs都有具体的了解。
五.不足和不确定
既然dvfs的预知方式和dpm都给用户修改频率的接口,那么是否需要同时配置两个接口呢?这要看我们的关注点了。
首先dpm的周期都会是很长(2s左右 ),那么在每个周期的开始时,我们都可以设定一个频率,这个频率将是这个周期的起始频率。
如果同时我们配置了dvfs。Dvfs的周期将是ms级的,或者更小。Dvfs对频率的调整的频率将远大于dpm的频率。所以dvfs将会按照自身的算法调整频率,是dpm的设置变得无效。
这样,我觉得如果配置了dvfs,就没有必要再配置dpm了。
或者,只配置dpm,不配置dvfs。
当然,在整个解析过程中,还有一些枝叶代码没有仔细查看,比如dpm中的idle,dvfs的table,以及其他一些。所以这个总结会有因此带来的错误和不足,这需要以后慢慢更新了。
终