首页 理论教育深入剖析Linux内核与设备驱动:电源管理核心框架

深入剖析Linux内核与设备驱动:电源管理核心框架

【摘要】:考虑到电源管理的需求涉及处理器和各种设备,一方面是处理器尽可能减少功耗,另一方面是设备尽可能减少功耗。图5-30Linux电源管理各个功能从图5-30可见Linux内核的电源管理功能有与处理器相关的CPUIdle和CPUFreq,也有与设备相关的runtime pm,另外还有与整个系统待机时SLM相关的低功耗电源管理功能。下面分别对这些功能框架进行介绍。具体的驱动同样会在SoC电源管理部分进行讲解。对具体设备的电源管理实现,将在设备驱动中进行详细分析。

Linux内核的电源管理部分在X86架构上有ACPI(高级配置与电源管理接口),但是广大的嵌入式设备并不支持ACPI,而对嵌入式设备来说电源管理的需求更强烈,所以急需相关的功能实现。考虑到电源管理的需求涉及处理器和各种设备,一方面是处理器尽可能减少功耗,另一方面是设备尽可能减少功耗。减少功耗的功能框架各种各样,一种功能框架是不能满足电源管理的需求的。所以Linux内核将电源管理分成不同的功能模块来实现,这样通过各个功能模块整体工作来满足电源管理的需求。Linux内核电源管理功能如图5-30所示。

978-7-111-49426-3-Chapter05-109.jpg

图5-30 Linux电源管理各个功能

从图5-30可见Linux内核的电源管理功能有与处理器相关的CPUIdle和CPUFreq,也有与设备相关的runtime pm,另外还有与整个系统待机时SLM(standby leakage management)相关的低功耗电源管理功能。但并不是只有这些功能,在时间管理中的dynamictick也属于降低功耗的功能。下面分别对这些功能框架进行介绍。

1ᤫCPUIdle

初始化的时候有过介绍,系统在没有任务需要调度的时候会执行cpu_idle,使处理器处于休息状态,其中系统提供了pm_idle接口实现各种idle的省电功能。CPUIdle会将其改为自己的接口cpuidle_idle_call,这样在内核进入idle时就会调用相应的接口,从而进入合适的状态。接下来分析一下相关的功能:

978-7-111-49426-3-Chapter05-110.jpg

978-7-111-49426-3-Chapter05-111.jpg

分析代码可见,具体的驱动要提供多种状态,这些状态是和QoS相关的。所谓QoS就是指对于idle来说不是简单地睡就可以了,要考虑醒来工作是否可以正常,例如进入不同的状态系统睡眠和唤醒延时是不同的,而idle只是短时间进入低功耗,相关的任务更重要,不能为了进入功耗更低的状态而影响正常的工作,这就需要QoS机制。在标记状态的同时要标记唤醒的时间,由governor来根据当前的系统状态选择合适的低功耗状态进入,既要休息好又不能影响工作。CPUIdle睡眠示意如图5-31所示。

从图5-31可见,系统要考虑进入哪种状态与能够睡眠的时间、sleep和wakeup延时都是相关的,能睡眠的时间越长越应该进入更高级别的低功耗状态。menu governor是通过每个处理器的下一次tick的时间来决定可进入低功耗的时间,从而进行选择。

978-7-111-49426-3-Chapter05-112.jpg

图5-31 CPUidle电源管理示意图

具体的设备驱动会在SoC电源管理部分进行讲解。

2ᤫCPUFreq

CPUFreq就是实现处理器的频率变化,其中会涉及DVFS来降低功耗,相关的硬件原理之前已经介绍。这里对Linux内核的软件实现进行分析。

在介绍无关性实现的时候,已经介绍了CPUFreq的框架(见图3-21),在CPUFreq中不管根据什么原则,是监视系统状态也好还是用户设置也好,在选择了相应的频率等级后最终都是通过__cpufreq_driver_target来进行频率设置的,其中会调用具体的设备target接口进入相应的频率等级。具体的驱动同样会在SoC电源管理部分进行讲解。

这里看看governor的实现,主要介绍ondemand governor。流程图如图5-32所示。

978-7-111-49426-3-Chapter05-113.jpg

图5-32 ondemand流程图

从图5-32可见,ondemand governor主要是监控CPU的使用率是否超过一定的值,如果是就提高主频,如果使用率很低就降低主频,从而实现根据使用率的动态调节以达到按需使用的功能。相关的参数配置在/sys/devices/system/cpu/cpu0/cpufreq/目录下,可以根据需要进行设置。

3ᤫSLM

这两部分功能都和设备相关。首先来看看设备模型相关的初始化部分,如图5-33所示。

978-7-111-49426-3-Chapter05-114.jpg

图5-33 设备模型电源管理相关初始化

从图5-33可见,其中涉及了设备Runtime pm的相关初始化,但是SLM的操作并不明显。其实设备的SLM相关操作已经隐含在device添加过程device_pm_add中,思考一下,SLM操作时要进入某种待机状态,其中会涉及所有的设备,自然将其放入系统的设备增加过程中。这样系统的SLM操作就可以通过设备模型统一管理了。下面来看看细节:

978-7-111-49426-3-Chapter05-115.jpg

978-7-111-49426-3-Chapter05-116.jpg

既然所有的设备都会通过device_add添加到设备模型中进行管理,那么相应的也会加入到设备功耗管理列表中进行管理。

那么系统如何进入待机状态呢?这就要看sys文件系统了,如图5-34所示。

978-7-111-49426-3-Chapter05-117.jpg

图5-34 待机控制框图

从图5-34可见,/sys/power目录下的state文件用于设置具体的待机状态。通过enter_state即可进入相应的状态,最终要进入待机suspend状态才会调用dpm_suspend_noirq。下面来看看实现细节:

978-7-111-49426-3-Chapter05-118.jpg

978-7-111-49426-3-Chapter05-119.jpg

从代码中可见,是想让所有的设备都进入待机状态,下面来看看通过device_suspend_noirq实现,设备模型对具体的设备进入相应待机状态的操作:

978-7-111-49426-3-Chapter05-120.jpg

从具体实现中可见,其操作逻辑是按照之前设备模型介绍,依据设备具体的情况进行的。对具体设备的电源管理实现,将在设备驱动中进行详细分析。SoC内部的接口设备通常都是platform_device,所以会执行bus的pm操作,具体如下:

978-7-111-49426-3-Chapter05-121.jpg

978-7-111-49426-3-Chapter05-122.jpg

而在suspend操作中会执行platform_pm_suspend_noirq,细节如下:

978-7-111-49426-3-Chapter05-123.jpg(www.chuimin.cn)

从中可见,具体的是调用相应的platform_driver接口函数,这样转换好处是使得platform_device只维护资源,而driver则是可以复用的操作接口。

再来看看使整个系统进入待机的接口:

978-7-111-49426-3-Chapter05-124.jpg

978-7-111-49426-3-Chapter05-125.jpg

978-7-111-49426-3-Chapter05-126.jpg

以上分析主要是进入待机状态最后阶段的操作流程,而要使得设备和系统进入待机状态,只提供一个操作接口其实现的复杂度就太高了,另外由于设备层次以及设备操作的复杂性,在进入待机状态之前需要进行必要的保护,所以电源管理操作就应该提供不同阶段的操作接口,正如在dev_pm_ops中所见的各种函数接口。具体的suspend操作则分为suspend prepare、suspend、suspend noirq等几个不同的阶段,并在dev_pm_ops定义了相应的接口。为了遍历所有的设备并执行相应的操作,dpm(derice power management)也提供了相应的接口,分别是dpm_prepare、dpm_suspend和dpm_suspend_noirq。这样一来所有的设备就可以通过分阶段的操作完成必要的保护操作后进入待机状态,恢复过程操作则是待机操作的逆过程。

从分析可知,整个系统的待机操作是从外围设备开始,最后到和系统电源管理最紧密的系统设备,所以从电源管理的角度设备也是分层的,外围设备就是设备模型中的device,而系统设备是sysdev(如CPU、memory、核心时钟源、时间管理部分等都是通过sysdev_regis-ter注册的,这与最小系统所见设备抽象后的实体基本一致)。另外内核也为芯片提供了基本的suspend_ops待机操作接口来进行芯片级别的特殊操作。

4ᤫruntime pm和device wake

下面看看设备的runtime pm是如何实现的。每个设备的runtime pm初始化工作由pm_runtime_init来完成,具体分析如下:

978-7-111-49426-3-Chapter05-127.jpg

978-7-111-49426-3-Chapter05-128.jpg

从代码分析中可见,主要是进行相关属性的初始化,另外初始化了用于异步执行runt-ime pm操作的work(pm_init中会初始化workqueue pm_wq来异步执行设备的runtime pm操作)以及相应的进行多个上下文同步的等待队列。考虑设备驱动是可以在多个上下文以及执行实体中运行,而驱动操作的设备物理上是唯一的实体,这就要求对设备进行的动态电源操作时需要进行同步。

设备的runtime pm操作的状态同步都是通过dev_pm_info来完成的,其被包含在device中,这样在任何上下文对设备进行runtime pm操作的时候都可以同步。dev_pm_info的细节如下:

978-7-111-49426-3-Chapter05-129.jpg

978-7-111-49426-3-Chapter05-130.jpg

978-7-111-49426-3-Chapter05-131.jpg

可见其中不仅包含设备runtime pm的本身状态信息,还包含不同操作方式(如同步、异步)的状态。设备的runtime pm操作最终是通过rpm_xxx来执行的,xxx代表不同的操作请求,主要是idle、suspend和resume。下面以rpm_suspend为例来分析具体的流程:

978-7-111-49426-3-Chapter05-132.jpg

978-7-111-49426-3-Chapter05-133.jpg

978-7-111-49426-3-Chapter05-134.jpg

978-7-111-49426-3-Chapter05-135.jpg

978-7-111-49426-3-Chapter05-136.jpg

从分析中可见,runtime pm的操作很好地考虑了多上下文执行以及多种操作方式的情况,保证对于设备操作的同步。

除此之外系统提供了一系列的操作接口来帮助驱动进行runtime pm操作,下面是一些接口的例子:

978-7-111-49426-3-Chapter05-137.jpg

具体设备的runtime pm的操作接口定义如下:

978-7-111-49426-3-Chapter05-138.jpg

与SLM的操作接口类似都在dev_pm_ops中进行定义,会有设备及各种总线以及class进行实现,实际的操作也是要按照层次执行。

下面列举了一个设备驱动中如何使用runtime pm的简单例子。

probe阶段时流程通常如下:

978-7-111-49426-3-Chapter05-139.jpg

需要进行工作时操作流程如下:

978-7-111-49426-3-Chapter05-140.jpg

没有具体的工作时可以直接通过pm_runtime_suspend来使得设备进入suspend状态。具体的操作由驱动根据实际情况调用runtime pm提供的各种接口来完成。

设备在电源管理方面除了suspend和resume的功能外,还有就是能够唤醒系统的能力。在唤醒系统的能力方面device中的struct dev_pm_info有can_wakeup属性表示wakeup的能力和wakeup属性表示唤醒源的信息等,通过这些属性来标记设备wakup的能力以及状态。另外提供了和wakup相关的接口如下:

978-7-111-49426-3-Chapter05-141.jpg

978-7-111-49426-3-Chapter05-142.jpg

这里的接口主要满足对设备在一定时间内唤醒系统的相关需求,具体的wakeup源是定时器,驱动可以根据需要进行调用。另外在设备suspend的时候还可以通过device_may_wakeup来检查设备是否可能唤醒系统而进行必要的唤醒设置(主要通过对set_irq_wake的封装接口set_irq_wake来实现)。

至此对主要的电源管理功能都进行了介绍和分析。