2024年4月28日发(作者:)

以下电子书来源于宋宝华 《Linux设备驱动开发详解:基于最新的Linux 4.0内核》

第19章 《Linux电源管理系统架构和驱动》

本章导读

Linux在消费电子领域的应用已经铺天盖地,而对于消费电子产品而言,省电是一个重

要的议题。

本章将介绍Linux设备树(Device Tree)的起源、结构和因为设备树而引起的驱动和BSP

变更。

19.1节阐述了Linux电源管理的总体架构。

19.2~19.8节分别论述了CPUFreq、CPUIdle、CPU热插拔以及底层的基础设施Regulator、

OPP以及电源管理的调试工具PowerTop。

19.9节讲解了系统Suspend to RAM的过程以及设备驱动如何提供对Suspend to RAM的

支持。

19.10节讲解了设备驱动的Runtime suspend。

本章是相对《Linux设备驱动开发详解(第2版)》全新的一章内容,也是Linux设备驱

动工程师必备的知识体系。

第十九章 Linux电源管理系统架构和驱动

1. Linux电源管理全局架构

Linux电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以

及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个

设备驱动都息息相关。

对于消费电子产品来说,电源管理相当重要。因此,这部分工作往往在开发周期中占据

相当大的比重,图19.1呈现了Linux内核电源管理的整体架构。大体可以归纳为如下几类:

1. CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq

2. CPU在系统空闲时根据空闲的情况进行低功耗模式的CPUIdle

3. 多核系统下CPU的热插拔支持

4. 系统和设备对于延迟的特别需求而提出申请的PM QoS,它会作用于CPUIdle的具体

策略

5. 设备驱动针对系统Suspend to RAM/Disk的一系列入口函数

6. SoC进入suspend状态、SDRAM自刷新的入口

7. 设备的runtime(运行时)动态电源管理,根据使用情况动态开关设备

8. 底层的时钟、稳压器、频率/电压表(OPP模块完成)支撑,各驱动子系统都可能用

用户空间

Power Manager

PM QoS

echo sysfsPower Applications

设备

系统级

suspend/resume

Runtime

suspend/resume

设备DVFS

(Devfreq)

CPUIdle

CPU

CPU hotplug

CPU DVFS

(CPUFreq)

基础设施

SoC平台级电源

操作callbacks

clk

regulator

OPP

图19.1 Linux电源管理系统架构

2. CPUFreq驱动

CPUFreq子系统位于drivers/cpufreq目录,负责进行运行过程中CPU频率和电压的动态

调整,即DVFS(Dynamic Voltage Frequency Scaling,动态电压频率调整)。运行时进行CPU

电压和频率调整的原因是:CMOS电路中的功耗与电压的平方成正比、与频率成正比

(),因此降低电压和频率可降低功耗。

CPUFreq的核心层位于drivers/cpufreq/cpufreq.c,它为各个SoC的CPUFreq驱动的实现

提供了一套统一的接口,并实现了一套notifier的机制,可以在CPUFreq的policy和频率改

变的时候向其他模块发出通知。另外,在CPU运行频率发生变化的时候,内核的

loops_per_jiffy常数也会发生相应变化。

1.1 SoC的CPUFreq驱动实现

具体的每个SoC的CPUFreq驱动实例只需要实现电压、频率表,以及从硬件层面完成这

些变化。

CPUFreq核心层提供了如下API供SoC注册自身的CPUFreq驱动:

int cpufreq_register_driver(struct cpufreq_driver *driver_data);

其参数为一个cpufreq_driver结构体指针,实际上,cpufreq_driver封装了一个具体的SoC

的CPUFreq驱动的主体,该结构体形如代码清单19.1。

代码清单19.1 cpufreq_driver结构体

1 struct cpufreq_driver {

2 struct module *owner;

3 char name[CPUFREQ_NAME_LEN];

4 u8 flags;

5

6 /* needed by all drivers */

7 int (*init) (struct cpufreq_policy *policy);

8 int (*verify) (struct cpufreq_policy *policy);

9

10 /* define one out of two */

11 int (*setpolicy) (struct cpufreq_policy *policy);

12 int (*target) (struct cpufreq_policy *policy,

13 unsigned inttarget_freq,

14 unsigned int relation);

15

16 /* should be defined, if possible */

17 unsigned int (*get) (unsigned intcpu);

18

19 /* optional */

20 unsigned int (*getavg) (struct cpufreq_policy *policy,

21 unsigned intcpu);

22 int (*bios_limit) (intcpu, unsigned int *limit);

23

24 int (*exit) (struct cpufreq_policy *policy);

25 int (*suspend) (struct cpufreq_policy *policy);

26 int (*resume) (struct cpufreq_policy *policy);

27 structfreq_attr **attr;

28 };

其中的owner成员一般被设置为THIS_MODULE;name成员是CPUFreq驱动名字,如

drivers/cpufreq/s5pv210-cpufreq.c设置name为"s5pv210",如drivers/cpufreq/omap-cpufreq.c

设置name为"omap";flags是一些暗示性的标志,譬如,若设置了CPUFREQ_CONST_LOOPS,

则是告诉内核loops_per_jiffy不会因为CPU频率的变化而变化。

init()成员是一个per-CPU初始化函数指针,每当一个新的CPU被注册进系统的时候,该

函数就被调用,该函数接受一个cpufreq_policy的指针参数,在init()成员函数中,可进行如

下设置:

policy->_freq

policy->_freq

该CPU支持的最小频率和最大频率(单位是kHz)。

policy->tion_latency

CPU进行频率切换所需要的延迟(单位是纳秒)

policy->cur

CPU当前频率

policy->policy

policy->governor

policy->min

policy->max

定义该CPU的缺省policy,以及缺省policy情况下该policy支持的最小、最大CPU频率。

verify()成员函数用于对用户的CPUFreq policy设置进行有效性验证和数据修正。每次用

户设定一个新policy时,该函数根据老的policy和新的policy,检验新policy设置的有效性

并对无效设置进行必要的修正。在该成员函数的具体实现中,常用到如下辅助函数:

cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned intmin_freq,

unsigned intmax_freq);

setpolicy()成员函数接受一个policy参数(包含policy->policy, policy->min和policy->max

等成员),实现了这个成员函数的CPU一般具备在一个范围(limit, 从policy->min到

policy->max)里面自动调整频率的能力。目前少数驱动如intel_pstate.c和longrun.c包含这

样的成员函数。而绝大多数CPU都不会实现此函数,一般实现target()成员函数,target()的

参数直接就是一个指定的频率。

target()成员函数用于将频率调整到一个指定的值,接受3个参数:policy、target_freq

和relation。target_freq是目标频率,实际驱动总是要设定真实的CPU频率到最接近于

target_freq,并且设定的频率必须位于policy->min到policy->max之间。在设定频率接近

target_freq的情况下,relation若为CPUFREQ_REL_L,暗示设置的频率应该大于或等于

target_freq;relation若为CPUFREQ_REL_H,暗示设置的频率应该小于或等于target_freq。

下表描述了setpolicy()和target()所针对的CPU以及调用方式上的区别:

setpolicy target

CPU具备在一定范围内独立调整频率的能力

"CPUfreq policy"调用到setpolicy,由CPU独

立在limit内调整频率

CPU只能被指定频率

由CPUFreq core根据系统负载和policy综合

决定目标频率

由于芯片内部PLL和分频器的关系,ARM SoC一般不具备独立调整频率的能力,往往SoC

的CPUFreq驱动会提供一个频率表,在该表的范围内进行变更,所以一般实现target()成员

函数。

CPUFreq核心层提供了一组频率表相关的辅助API。

int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy,

struct cpufreq_frequency_table *table);

它是cpufreq_driver的init()成员函数的助手,用于将policy->min和policy->max设置为

与_freq和_freq相同的值。

int cpufreq_frequency_table_verify(struct cpufreq_policy *policy,

struct cpufreq_frequency_table *table);

它是cpufreq_driver的verify()成员函数的助手,确保至少1个有效的CPU频率位于

policy->min到policy->max范围内。

int cpufreq_frequency_table_target(struct cpufreq_policy *policy,

struct cpufreq_frequency_table *table,

unsigned int target_freq,

unsigned int relation,

unsigned int *index);

它是cpufreq_driver的target()成员函数的助手,返回需要设定的频率在频率表中的索引。

省略掉具体的细节,1个SoC的CPUFreq驱动的实例drivers/cpufreq/s3c64xx-cpufreq.c

的核心结构如代码清单19.2。

代码清单19.2 S3C64XX的CPUFreq驱动

001 static unsigned long regulator_latency;

002

003 struct s3c64xx_dvfs {

004 unsigned intvddarm_min;

005 unsigned intvddarm_max;

006 };

007

008 static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = {

009 [0] = { 1000000, 1150000 },

010 …

011 [4] = { 1300000, 1350000 },

012 };

013

014 static struct cpufreq_frequency_table s3c64xx_freq_table[] = {

015 { 0, 66000 },

016 { 0, 100000 },

017 …

018 { 0, CPUFREQ_TABLE_END },

019 };

020

021 static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy)

022 {

023 if (policy->cpu != 0)

024 return -EINVAL;

025

026 return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table);

027 }

028

029 static unsigned int s3c64xx_cpufreq_get_speed(unsigned intcpu)

030 {

031 if (cpu != 0)

032 return 0;

033

034 return clk_get_rate(armclk) / 1000;

035 }

036

037 static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy,

038 unsigned int target_freq,

039 unsigned int relation)

040 {

041 …

042 ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table,

043 target_freq, relation, &i);

044 …

045 = 0;

046 = clk_get_rate(armclk) / 1000;

047 = s3c64xx_freq_table[i].frequency;

048 = 0;

049 dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index];

050

051 if ( == )

052 return 0;

053

054 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);

055

056 if (vddarm&&>) {

057 ret = regulator_set_voltage(vddarm,

058 dvfs->vddarm_min,

059 dvfs->vddarm_max);

060 …

061 }

062

063 ret = clk_set_rate(armclk, * 1000);

064 …

065 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);

066

067 if (vddarm&&<) {

068 ret = regulator_set_voltage(vddarm,

069 dvfs->vddarm_min,

070 dvfs->vddarm_max);

071 …

072 }

073

074 return 0;

075 }

076

077 static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy)

078 {

079 …

080 armclk = clk_get(NULL, "armclk");

081 …

082 vddarm = regulator_get(NULL, "vddarm");

083 …

084 s3c64xx_cpufreq_config_regulator();

085

086 freq = s3c64xx_freq_table;

087 while (freq->frequency != CPUFREQ_TABLE_END) {

088 unsigned long r;

089 …

090 }

091

092 policy->cur = clk_get_rate(armclk) / 1000;

093 policy->tion_latency = (500 * 1000) + regulator_latency;

094 ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table);

095 …

096 return ret;

097 }

098

099 staticstruct cpufreq_driver s3c64xx_cpufreq_driver = {

100 .owner = THIS_MODULE,

101 .flags = 0,

102 .verify = s3c64xx_cpufreq_verify_speed,

103 .target = s3c64xx_cpufreq_set_target,

104 .get = s3c64xx_cpufreq_get_speed,

105 .init = s3c64xx_cpufreq_driver_init,

106 .name = "s3c",

107 };

108

109 static int __init s3c64xx_cpufreq_init(void)

110 {

111 return cpufreq_register_driver(&s3c64xx_cpufreq_driver);

112 }

113 module_init(s3c64xx_cpufreq_init);

第37行s3c64xx_cpufreq_set_target()就是完成目标频率设置的函数,它调用了

cpufreq_frequency_table_target()从s3c64xx支持的频率表s3c64xx_freq_table里找到合适的频

率。在具体的频率和电压设置环节,用的都是Linux的标准API regulator_set_voltage()和

clk_set_rate()之类的函数。

第111行在模块初始化的时候透过cpufreq_register_driver()注册了cpufreq_driver的实例,

第94行,CPUFreq的初始化阶段调用cpufreq_frequency_table_cpuinfo()注册了频率表。关于

频率表,比较新的内核喜欢使用后面章节将介绍的OPP。

在当前的Linux内核中,存在一个通用的cpufreq-dt驱动,位于drivers/cpufreq/cpufreq-dt.c,

很多SoC都可以兼容这个驱动,“grep”arch/arm/的代码发现mach-imx/imx27-dt.c、

mach-imx/mach-imx51.c、mach-imx/mach-imx53.c、mach-mvebu/pmsu.c、mach-omap2/pm.c、

mach-rockchip/rockchip.c、mach-shmobile/cpufreq.c、mach-sunxi/sunxi.c、mach-sunxi/sunxi.c

都注册了cpufreq-dt平台设备。

1.2 CPUFreq的policy

SoC CPUFreq驱动只是设定了CPU的频率参数,以及提供了设置频率的途径,但是它并

不会管自身CPU究竟应该运行在什么频率上。究竟频率依据什么样的标准,进行何种变化,

则完全由CPUFreq的policy(策略)决定,这些policy如下表:

CPUFreq的policy

cpufreq_ondemand

cpufreq_performance

cpufreq_conservative

cpufreq_powersave

cpufreq_userspace

策略实现方法

平时以低速方式运行,当系统负载提高时候

按需自动提高频率

CPU以最高频率运行,即scaling_max_freq

字面含义是传统的、保守的,跟ondemand

相似,区别在于动态频率变更的时候采用渐

进的方式

CPU以最低频率运行,即scaling_min_freq

让root用户透过sys结点scaling_setspeed设

置频率

在Android系统中,则增加了1个interactive policy,该policy适合对延迟敏感的UI交互

任务,当有UI交互任务的时候,该policy会更加激进和及时地调整CPU频率。

总地来说,系统的状态以及CPUFreq的Policy共同决定了CPU频率跳变的target,CPUFreq

核心层并将target频率传递给底层具体SoC的CPUFreq驱动,该驱动修改硬件,完成频率的

变换,如图19.2所示。

系统负载

CPUFreq CoreCPUFreq Policy

SoC CPUFreq驱动

CPU硬件

图19.2 CPUFreq、系统负载、policy与调频

用户空间一般可通过/sys/devices/system/cpu/cpux/cpufreq结点来设置CPUFreq。譬如,

我们要设置CPUFreq到700MHz,采用userspace策略,则运行如下命令:

# echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# echo 700000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed

1.3 CPUFreq的性能测试和调优

Linux 3.1以后的内核已经将cpupower-utils工具集放入内核的tools/power/cpupower目

录,该工具集当中的cpufreq-bench工具可帮助工程师分析采用CPUFreq后对系统性能的影

响。

cpufreq-bench工具的工作原理是模拟系统运行时候的“空闲——忙——空闲——忙”场

景,从而触发系统的动态频率变化,然后计算在使用ondemand、conservative、interactive

等policy的情况下,做同样的运算与在performance高频模式下做同样运算完成任务的时间

比例。

交叉编译该工具后,可放入目标电路板文件系统的/usr/sbin/等目录,运行该工具:

# cpufreq-bench -l 50000 -s 100000 -x 50000 -y 100000 -g ondemand -r 5 -n 5 -v

会输出一系列的结果,我们提取其中的Round n这样的行,它表明了-g ondemand选项

中设定的ondemand policy相对于performance policy的性能比例,假设值为:

Round 1 - 39.74%

Round 2 - 36.35%

Round 3 - 47.91%

Round 4 - 54.22%

Round 5 - 58.64%

显然不太理想,我们在同样的平台下采用Android的interactive policy,得到新的测试结

果:

Round 1 - 72.95%

Round 2 - 87.20%

Round 3 - 91.21%

Round 4 - 94.10%

Round 5 - 94.93%

一般的目标是采用CPUFreq动态调整频率和电压后,性能应该为performance这个高性

能policy情况下的90%左右比较理想。

1.4 CPUFreq通知

两种情况下,CPUFreq子系统会发出通知:CPUFreq的policy变化或者CPU运行频率变

化。

在policy变化的过程中,会发送3次通知:

 CPUFREQ_ADJUST:所有注册的notifier可以根据硬件或者温度的考虑去修改limit(即

policy->min和policy->max);

 CPUFREQ_INCOMPATIBLE:除非前面的policy设定可能会导致硬件的出错,被注册的

notifier才可以改变limit等设定;

 CPUFREQ_NOTIFY:所有注册的notifier都会被告知新的policy已经被设置。

在频率变化的过程中,会发送2次通知:

 CPUFREQ_PRECHANGE:准备进行频率变更;

 CPUFREQ_POSTCHANGE:已经完成频率变更。

notifier中的第3个参数是一个cpufreq_freqs的结构体,包含cpu(CPU号)、old(过去

的频率)和new(现在的频率)这3个成员。发送CPUFREQ_PRECHANGE和

CPUFREQ_POSTCHANGE的代码如下:

srcu_notifier_call_chain(&cpufreq_transition_notifier_list,

CPUFREQ_PRECHANGE, freqs);

srcu_notifier_call_chain(&cpufreq_transition_notifier_list,

CPUFREQ_POSTCHANGE, freqs);

如果某模块关心CPUFREQ_PRECHANGE或CPUFREQ_POSTCHANGE事件,可简单地使用

Linux notifier机制监控之。譬如,drivers/video/sa1100fb.c在CPU频率变化过程中需对自身

硬件进行相关设置,则它注册了notifier并在CPUFREQ_PRECHANGE和CPUFREQ_POSTCHANGE

情况下分别进行不同的处理,如代码清单19.3。

代码清单19.3 CPUFreq notifier案例

01 fbi->freq_er_call = sa1100fb_freq_transition;

02 cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);

03 ...

04 sa1100fb_freq_transition(structnotifier_block *nb, unsigned long val,

05 void *data)

06 {

07 struct sa1100fb_info *fbi = TO_INF(nb, freq_transition);

08 struct cpufreq_freqs *f = data;

09 u_intpcd;

10

11 switch (val) {

12 case CPUFREQ_PRECHANGE:

13 set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE);

14 break;

15 case CPUFREQ_POSTCHANGE:

16 pcd = get_pcd(fbi->ck, f->new);

17 fbi->reg_lccr3 = (fbi->reg_lccr3& ~0xff) | LCCR3_PixClkDiv(pcd);

18 set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE);

19 break;

20 }

21 return 0;

22 }

此外,如果在系统suspend/resume的过程中CPU频率会发生变化,则CPUFreq子系统

也会发出CPUFREQ_SUSPENDCHANGE和CPUFREQ_RESUMECHANGE这2个通知。

值得一提的是,除了CPU以外,一些非CPU设备也支持多个操作频率和电压,存在多

个OPP。Linux 3.2之后的内核也支持针对这种非CPU设备的DVFS,该套子系统为Devfreq。

与CPUFreq存在一个drivers/cpufreq目录相似,在内核也存在一个drivers/devfreq的目录。

3. CPUIdle驱动

目前的ARM SoC大多支持几个不同的IDLE级别,CPUIdle驱动子系统存在的目的就是对

这些IDLE状态进行管理,并根据系统的运行情况进入不同的IDLE级别。具体SoC的底层

CPUIdle驱动实现则提供一个类似于CPUFreq驱动频率表的IDLE级别表,并实现各种不同IDLE

状态的进入和退出流程。

对于Intel系列笔记本电脑而言,支持ACPI(Advanced Configuration and Power Interface,

高级配置和电源管理接口),一般有4个不同的C状态(其中C0为操作状态,C1是Halt状

态,C2是Stop-Clock状态,C3是Sleep状态):

状态

C0

C1

C2

C3

而对于ARM而言,各个SoC对于IDLE的实现方法差异比较大,最简单的IDLE级别莫过

于将CPU核置于WFI(等待中断发生)状态,因此默认情况下,若SoC未实现自身的芯片

级CPUIdle驱动,则会进入cpu_do_idle(),对于ARM V7而言,其实现位于

arch/arm/mm/proc-v7.S:

ENTRY(cpu_v7_do_idle)

dsb @ WFI may enter a low-power mode

wfi

mov pc, lr

ENDPROC(cpu_v7_do_idle)

功耗(mW)

-1

1000

500

100

延迟(uS)

0

1

1

57

与CPUFreq类似,CPUIdle的核心层提供了如下API用于注册一个cpuidle_driver的实例:

int cpuidle_register_driver(struct cpuidle_driver *drv);

并提供了如下API来注册一个cpuidle_device:

int cpuidle_register_device(struct cpuidle_device *dev);

CPUIdle驱动必须针对每个CPU注册相应的cpuidle_device,这意味着对于多核CPU而

言,需要针对每个CPU注册一次。

cpuidle_register_driver()接受1个cpuidle_driver结构体的指针参数,该结构体是CPUIdle

驱动的主体,其定义如代码清单19.4。

代码清单19.4 cpuidle_driver结构体

01 struct cpuidle_driver {

02 const char *name;

03 struct module *owner;

04

05 unsigned int power_specified:1;

06 /* set to 1 to use the core cpuidle time keeping (for all states). */

07 unsigned int en_core_tk_irqen:1;

08 struct cpuidle_state states[CPUIDLE_STATE_MAX];

09 int state_count;

10 int safe_state_index;

11 };

该结构体的关键成员是1个cpuidle_state的表,其实就是存储各种不同IDLE级别的信

息,它的定义如代码清单19.5。

代码清单19.5 cpuidle_state结构体

01 struct cpuidle_state {

02 char name[CPUIDLE_NAME_LEN];

03 char desc[CPUIDLE_DESC_LEN];

04

05 unsigned int flags;

06 unsigned intexit_latency; /* in US */

07 int power_usage; /* in mW */

08 unsigned inttarget_residency; /* in US */

09 bool disabled; /* disabled on all CPUs */

10

11 int (*enter) (struct cpuidle_device *dev,

12 struct cpuidle_driver *drv,

13 int index);

14

15 int (*enter_dead) (struct cpuidle_device *dev, int index);

16 };

name和desc是该IDLE状态的名称和描述,exit_latency是退出该IDLE状态需要的延迟,

enter()是进入该IDLE状态的实现方法。

省略细节,一个具体的SoC的CPUIdle驱动实例可见于arch/arm/mach-ux500/cpuidle.c

(最新的内核已经将代码转移到了drivers/cpuidle/cpuidle-ux500.c),它有2个IDLE级别,即

“WFI”和“ApIdle”,其具体实现框架如代码清单19.6。

代码清单19.6 ux500 CPUIdle驱动案例

01 staticatomic_t master = ATOMIC_INIT(0);

02 static DEFINE_SPINLOCK(master_lock);

03 static DEFINE_PER_CPU(struct cpuidle_device, ux500_cpuidle_device);

04

05 static inline int ux500_enter_idle(struct cpuidle_device *dev,

06 struct cpuidle_driver *drv, int index)

07 {

08 …

09 }

10

11 staticstruct cpuidle_driver ux500_idle_driver = {

12 .name = "ux500_idle",

13 .owner = THIS_MODULE,

14 .en_core_tk_irqen = 1,

15 .states = {

16 ARM_CPUIDLE_WFI_STATE,

17 {

18 .enter = ux500_enter_idle,

19 .exit_latency = 70,

20 .target_residency = 260,

21 .flags = CPUIDLE_FLAG_TIME_VALID,

22 .name = "ApIdle",

23 .desc = "ARM Retention",

24 },

25 },

26 .safe_state_index = 0,

27 .state_count = 2,

28 };

29

30 /*

31 * For each cpu, setup the broadcast timer because we will

32 * need to migrate the timers for the states >= ApIdle.

33 */

34 static void ux500_setup_broadcast_timer(void *arg)

35 {

36 intcpu = smp_processor_id();

37 clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu);

38 }

39

40 int __init ux500_idle_init(void)

41 {

42 …

43 ret = cpuidle_register_driver(&ux500_idle_driver);

44 …

45 for_each_online_cpu(cpu) {

46 device = &per_cpu(ux500_cpuidle_device, cpu);

47 device->cpu = cpu;

48 ret = cpuidle_register_device(device);

49 …

50 }

51 …

52 }

53 device_initcall(ux500_idle_init);

与CPUFreq类似,在CPUIdle子系统也有对应的governor来抉择何时进入何种IDLE级

别的策略,这些governor包括CPU_IDLE_GOV_LADDER、CPU_IDLE_GOV_MENU。LADDER在

进入和退出IDLE级别的时候是步进的,它以过去的IDLE时间作为参考,而MENU总是根据

预期的空闲时间直接进入目标IDLE级别。前者可适用于没有采用动态时间节拍的系统(即

没有选择NO_HZ的系统),不依赖于NO_HZ配置选项,后者依赖于内核的NO_HZ选项。

下图演示了LADDER步进从C0进入C3,而MENU则可能直接从C0跳入C3:

图19.3 LADDER与MENU的区别

CPUIdle子系统还透过sys向userspace导出了一些结点:

 一类是针对整个系统的/sys/devices/system/cpu/cpuidle,透过其中的current_driver、

current_governor、available_governors等结点可以获取或设置CPUIdle的驱动信息以

及governor。

 一类是针对每个CPU的/sys/devices/system/cpu/cpux/cpuidle/,通过子结点暴露各个

online的CPU中每个不同IDLE级别的name、desc、power、latency等信息。

综合以上的各个要素,可以给出Linux CPUIdle子系统的总体架构如图19.4。

/sys/devices/system/cpu/cpux/cpuidle

/sys/devices/system/cpu/cpuidle

laddermenu

CPUIdle Core

acpi-cpuidle

SoC级别

cpuidle

ARM级别

cpu_do_idle

图19.4 CPUIdle子系统架构

4. PowerTop

PowerTop是一款开源的用于进行电量消耗分析和电源管理诊断的工具,其主页位于Intel

开源技术中心的/powertop/,维护者是

Arjan van de Ven

Kristen Accardi。

PowerTop可分析系统中软件的功耗以便找到功耗大户,也可显示系统中不同的C状态(与CPUIdle

驱动对应)和P状态(与CPUFreq驱动对应)的时间比例,采用了基于TAB的界面风格,如图

10.5。

图19.5 PowerTOP

5. Regulator驱动

Regulator是Linux系统中电源管理的基础设施之一,用于稳压电源的管理,是各种驱动

子系统中设置电压的标准接口。前面介绍的CPUFreq驱动就经常使用它来设定电压,比如代

码清单19.2的57-59行。

而Regulator则可以管理系统中的供电单元即稳压器(如LDO,即low dropout regulator,

低压差线性稳压器),并提供获取和设置这些供电单元电压的接口。一般在ARM电路板上,

各个稳压器和设备会形成一个供电树形结构,如图19.6所示。

电源

(power supply)

硬件regulator1硬件regulator2

设备1

设备1

设备2

设备2

硬件regulator3

设备1

设备2

图19.6 Regulator树型结构

Linux的Regulator子系统提供如下API用于注册/注销一个稳压器:

struct regulator_dev * regulator_register(const struct

*regulator_desc, const struct regulator_config *config);

void regulator_unregister(struct regulator_dev *rdev);

regulator_desc

regulator_register()函数的2个参数分别是regulator_desc结构体和regulator_config结构

体的指针。

regulator_desc结构体是对这个稳压器属性和操作的封装,如代码清单19.7。

代码清单19.7 regulator_desc结构体

01 struct regulator_desc {

02 const char *name; /* regulator的名字 */

03 const char *supply_name; /* regulator supply的名字 */

04 int id;

05 unsigned n_voltages;

06 struct regulator_ops *ops;

07 int irq;

08 enum regulator_type type; /* 是电压还是电流Regulator */

09 struct module *owner;

10

11 unsigned int min_uV; /* 线性映射情况下最低的selector的电压 */

12 unsigned int uV_step; /* 线性映射情况下每步增加/减小的电压 */

13 unsigned int ramp_delay; /* 电压改变后稳定下来所需时间 */

14

15 const unsigned int *volt_table; /* 基于表映射情况下的电压映射表 */

16

17 unsigned int vsel_reg;

18 unsigned int vsel_mask;

19 unsigned int enable_reg;

20 unsigned int enable_mask;

21 unsigned int bypass_reg;

22 unsigned int bypass_mask;

23

24 unsigned int enable_time;

25 };

上述结构体中的regulator_ops指针ops是对这个稳压器硬件操作的封装,其中包含获

取、设置电压等的成员函数,如代码清单19.8。

代码清单19.8 regulator_ops结构体

01 struct regulator_ops {

02 /* enumerate supported voltages */

03 int (*list_voltage) (struct regulator_dev *, unsigned selector);

04

05 /* get/set regulator voltage */

06 int (*set_voltage) (struct regulator_dev *, int min_uV, int max_uV,

07 unsigned *selector);

08 int (*map_voltage)(struct regulator_dev *, int min_uV, int max_uV);

09 int (*set_voltage_sel) (struct regulator_dev *, unsigned selector);

10 int (*get_voltage) (struct regulator_dev *);

11 int (*get_voltage_sel) (struct regulator_dev *);

12

13 /* get/set regulator current */

14 int (*set_current_limit) (struct regulator_dev *,

15 int min_uA, int max_uA);

16 int (*get_current_limit) (struct regulator_dev *);

17

18 /* enable/disable regulator */

19 int (*enable) (struct regulator_dev *);

20 int (*disable) (struct regulator_dev *);

21 int (*is_enabled) (struct regulator_dev *);

22

23 …

24 };

在drivers/regulator目录下,包含大量的电源芯片对应的Regulator驱动,如Dialog的

DA9052、Intersil的ISL6271A、ST-Ericsson的TPS61050/61052、Wolfon的WM831x系列等,

它同时提供了一个dummy的Regulator驱动作为参考,如代码清单19.9。

代码清单19.9 dummy的regulator驱动

01 struct regulator_dev *dummy_regulator_rdev;

02 static struct regulator_init_data dummy_initdata;

03 static struct regulator_ops dummy_ops;

04 static struct regulator_desc dummy_desc = {

05 .name = "regulator-dummy",

06 .id = -1,

07 .type = REGULATOR_VOLTAGE,

08 .owner = THIS_MODULE,

09 .ops = &dummy_ops,

10 };

11

12 static int __devinit dummy_regulator_probe(struct platform_device *pdev)

13 {

14 struct regulator_config config = { };

15 int ret;

16

17 = &pdev->dev;

18 _data = &dummy_initdata;

19

20 dummy_regulator_rdev = regulator_register(&dummy_desc, &config);

21 if (IS_ERR(dummy_regulator_rdev)) {

22 ret = PTR_ERR(dummy_regulator_rdev);

23 pr_err("Failed to register regulator: %dn", ret);

24 return ret;

25 }

26

27 return 0;

28 }

Linux的Regulator子系统提供消费者(Consumer)API以便让其他的驱动获取、设置、

关闭和使能稳压器:

struct regulator * regulator_get(struct device *dev, const char *id);

struct regulator * devm_regulator_get(struct device *dev, const char *id);

struct regulator *regulator_get_exclusive(struct device *dev, const char *id);

void regulator_put(struct regulator *regulator);

void devm_regulator_put(struct regulator *regulator);

int regulator_enable(struct regulator *regulator);

int regulator_disable(struct regulator *regulator);

int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV);

int regulator_get_voltage(struct regulator *regulator);

这些消费者API的地位大致与GPIO子系统的gpio_request()、Clock子系统的clk_get()、

dmaengine子系统的dmaengine_submit()等相当,属于基础设施。

6. OPP

当今的SoC一般包含很多集成的组件,在系统运行过程中,并不需要所有的模块都运行

于最高频率和最高性能。在SoC内,某些domain可以运行在较低的频率和电压,而其他

domain可以运行在较高的频率和电压,某个domain所支持的<频率,电压>对的集合被称为

Operating Performance Point(缩写为OPP)。

int opp_add(struct device *dev, unsigned long freq, unsigned long u_volt);

目前,TI OMAP CPUFreq驱动的底层就使用了OPP这种机制来获取CPU所支持的频率和

电压列表。在开机的过程中,TI OMAP4芯片会注册针对CPU设备的OPP表(代码位于

arch/arm/mach-omap2/),如代码清单19.10。

代码清单19.10 TI OMAP4 CPU的OPP表

01 static struct omap_opp_def __initdata omap44xx_opp_def_list[] = {

02 /* MPU OPP1 - OPP50 */

03 OPP_INITIALIZER("mpu", true, 300000000, OMAP4430_VDD_MPU_OPP50_UV),

04 /* MPU OPP2 - OPP100 */

05 OPP_INITIALIZER("mpu", true, 600000000, OMAP4430_VDD_MPU_OPP100_UV),

06 /* MPU OPP3 - OPP-Turbo */

07 OPP_INITIALIZER("mpu", true, 800000000, OMAP4430_VDD_MPU_OPPTURBO_UV),

08 /* MPU OPP4 - OPP-SB */

09 OPP_INITIALIZER("mpu", true, 1008000000, OMAP4430_VDD_MPU_OPPNITRO_UV),

10 …

11 };

12 /**

13 * omap4_opp_init() - initialize omap4 opp table

14 */

15 int __init omap4_opp_init(void)

16 {

17 …

18 r = omap_init_opp_table(omap44xx_opp_def_list,

19 ARRAY_SIZE(omap44xx_opp_def_list));

20

21 return r;

22 }

23 device_initcall(omap4_opp_init);

24 int __init omap_init_opp_table(struct omap_opp_def *opp_def,

25 u32 opp_def_size)

26 {

27 …

28 /* Lets now register with OPP library */

29 for (i = 0; i < opp_def_size; i++, opp_def++) {

30 …

31 if (!strncmp(opp_def->hwmod_name, "mpu", 3)) {

32 /*

33 * All current OMAPs share voltage rail and

34 * clock source, so CPU0 is used to represent

35 * the MPU-SS.

36 */

37 dev = get_cpu_device(0);

38 } …

39 r = opp_add(dev, opp_def->freq, opp_def->u_volt);

40 …

41 }

42 return 0;

43 }

针对device结构体指针dev对应的domain增加一个新的OPP,参数freq和u_volt即为

该OPP对应的频率和电压。

int opp_enable(struct device *dev, unsigned long freq);

int opp_disable(struct device *dev, unsigned long freq);

上述API用于使能和禁止某个OPP,一旦被disable,其available将成为false,之后有

设备驱动想设置为这个OPP就不再可能了。譬如,当温度超过某个范围后,系统不允许1GHz

的工作频率,可采用类似代码:

if (cur_temp > temp_high_thresh) {

/* Disable 1GHz if it was enabled */

rcu_read_lock();

opp = opp_find_freq_exact(dev, 1000000000, true);

rcu_read_unlock();

/* just error check */

if (!IS_ERR(opp))

ret = opp_disable(dev, 1000000000);

else

goto try_something_else;

}

上述代码中调用的opp_find_freq_exact()用于寻找与一个确定频率和available匹配的

OPP,其原型为:

struct opp *opp_find_freq_exact(struct device *dev, unsigned long freq,

bool available);

另外,Linux还提供2个变体,opp_find_freq_floor()用于寻找1个OPP,它的频率向上

接近或等于指定的频率;opp_find_freq_ceil()用于寻找1个OPP,它的频率向下接近或等于

指定的频率,这2个函数的原型为:

struct opp *opp_find_freq_floor(struct device *dev, unsigned long *freq);

struct opp *opp_find_freq_ceil(struct device *dev, unsigned long *freq);

我们可用下面的代码分别寻找1个设备的最大和最小工作频率:

freq = ULONG_MAX;

rcu_read_lock();

opp_find_freq_floor(dev, &freq);

rcu_read_unlock();

freq = 0;

rcu_read_lock();

opp_find_freq_ceil(dev, &freq);

rcu_read_unlock();

在频率降低的同时,其支撑该频率运行所需的电压也往往可以动态调低;反之,则可能

需要调高,下面这2个API分别用于获取某OPP对应的电压和频率:

unsigned long opp_get_voltage(struct opp *opp);

unsigned long opp_get_freq(struct opp *opp);

举个例子,当某CPUFreq驱动想将CPU设置为某一频率的时候,它可能会同时设置电

压,其代码流程为:

soc_switch_to_freq_voltage(freq)

{

/* do things */

rcu_read_lock();

opp = opp_find_freq_ceil(dev, &freq);

v = opp_get_voltage(opp);

rcu_read_unlock();

if (v)

regulator_set_voltage(.., v);

/* do other things */

}

如下简单的API可用于获取某设备所支持的OPP的个数:

int opp_get_opp_count(struct device *dev);

前面提到,TI OMAP CPUFreq驱动的底层就使用了OPP这种机制来获取CPU所支持的频

率和电压列表。它在omap_init_opp_table()函数中添加了相应的OPP,在TI OMAP芯片的

CPUFreq驱动drivers/cpufreq/omap-cpufreq.c中,则借助了快捷函数opp_init_cpufreq_table()

来依据前面注册的OPP建立CPUFreq的频率表:

static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)

{

if (!freq_table)

result = opp_init_cpufreq_table(mpu_dev, &freq_table);

}

而在CPUFreq驱动的target成员函数omap_target()中,则使用OPP相关的API来获取

了频率和电压:

static int omap_target(struct cpufreq_policy *policy,

unsigned int target_freq,

unsigned int relation)

{

if (mpu_reg) {

opp = opp_find_freq_ceil(mpu_dev, &freq);

volt = opp_get_voltage(opp);

}

}

drivers/cpufreq/omap-cpufreq.c相对来说较为规范,它在<频率,电压>表方面底层使用

了OPP,在设置电压的时候又使用了规范的Regulator API。

比较新的驱动一般不太喜欢直接在代码里面固话OPP表,而是喜欢在相应的结点添加

operating-points属性,如中的:

cpus {

#size-cells = <0>;

#address-cells = <1>;

cpu: cpu@0 {

device_type = "cpu";

compatible = "arm,arm926ej-s";

operating-points = <

/* kHz uV */

266000 1300000

399000 1450000

>;

clock-latency = <62500>;

clocks = <&clks IMX27_CLK_CPU_DIV>;

voltage-tolerance = <5>;

};

};

如果CPUFreq的变化可以使用非常标准的regulator、clk API,我们甚至可以直接使用

drivers/cpufreq/cpufreq-dt.c这个驱动。这样只需要在CPU结点上填充好频率电压表,然后

在平台代码里面里面注册cpufreq-dt设备就可以了,arch/arm/mach-imx/imx27-dt.c、

arch/arm/mach-imx/mach-imx51.c中可以找到类似的例子:

static void __init imx27_dt_init(void)

{

struct platform_device_info devinfo = { .name = "cpufreq-dt", };

of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

platform_device_register_full(&devinfo);

}

7. PM QoS

Linux内核的PM QoS系统针对内核和应用程序提供了一套接口,透过这个接口,用户

可以设定自身对性能的期望。一类是系统级的需求,透过cpu_dma_latency, network_latency,

network_throughput这些参数来设定;一类是单个设备可以根据自身的性能需求发起

per-device的PM QoS请求。

在内核空间,通过pm_qos_add_request()函数可以注册PM QoS 请求:

void pm_qos_add_request(struct pm_qos_request *req,

int pm_qos_class, s32 value);

通过pm_qos_update_request() 函数可以更新已注册的PM QoS 请求:

void pm_qos_update_request(struct pm_qos_request *req,

s32 new_value);

void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value,

unsigned long timeout_us);

通过pm_qos_remove_request()函数可以删除已注册的PM QoS 请求:

void pm_qos_remove_request(struct pm_qos_request *req);

譬如在drivers/media/platform/via-camera.c这个摄像头驱动中,当摄像头开启后,通过

如下语句可以阻止CPU进入C3级别的深度IDLE:

static int viacam_streamon(struct file *filp, void *priv, enum v4l2_buf_type t)

{

pm_qos_add_request(&cam->qos_request, PM_QOS_CPU_DMA_LATENCY, 50);

}

这是因为,在CPUIdle子系统中,会根据PM_QOS_CPU_DMA_LATENCY请求的情况选择

合适的C状态,如drivers/cpuidle/governors/ladder.c中的ladder_select_state()就会判断目标

C状态的exit_latency与QoS要求的关系,如代码清单19.11。

代码清单19.11 CPUIdle LADDER governor对QoS的判断

01 static int ladder_select_state(struct cpuidle_driver *drv,

02 struct cpuidle_device *dev)

03 {

04 …

05 int latency_req = pm_qos_request(PM_QOS_CPU_DMA_LATENCY);

06

07 …

08

09 /* consider promotion */

10 if (last_idx < drv->state_count - 1 &&

11 !drv->states[last_idx + 1].disabled &&

12 !dev->states_usage[last_idx + 1].disable &&

13 last_residency > last_state->ion_time &&

14 drv->states[last_idx + 1].exit_latency <= latency_req) {

15 last_state->ion_count++;

16 last_state->on_count = 0;

17 if(last_state->ion_count>=

18 last_state->ion_count) {

19 ladder_do_selection(ldev, last_idx, last_idx + 1);

20 return last_idx + 1;

21 }

22 }

23 …

24 }

LADDER在选择是否进入更深层次的C状态时,会比较C状态的exit_latency要小于透过

pm_qos_request(PM_QOS_CPU_DMA_LATENCY)得到的PM QoS请求的延迟,见第14行。

同样的逻辑也出现于drivers/cpuidle/governors/menu.c中,如代码清单19.12的第18~19

行。

代码清单19.12 CPUIdle MENU governor对QoS的判断

01 static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev)

02 {

03 struct menu_device *data = &__get_cpu_var(menu_devices);

04 int latency_req = pm_qos_request(PM_QOS_CPU_DMA_LATENCY);

05 …

06 /*

07 * Find the idle state with the lowest power while satisfying

08 * our constraints.

09 */

10 for (i = CPUIDLE_DRIVER_STATE_START; i < drv->state_count; i++) {

11 struct cpuidle_state *s = &drv->states[i];

12 struct cpuidle_state_usage *su = &dev->states_usage[i];

13

14 if (s->disabled || su->disable)

15 continue;

16 if (s->target_residency > data->predicted_us)

17 continue;

18 if (s->exit_latency > latency_req)

19 continue;

20 if (s->exit_latency * multiplier > data->predicted_us)

21 continue;

22

23 if (s->power_usage < power_usage) {

24 power_usage = s->power_usage;

25 data->last_state_idx = i;

26 data->exit_us = s->exit_latency;

27 }

28 }

29

30 return data->last_state_idx;

31 }

还是回到drivers/media/platform/via-camera.c,当摄像头关闭后,它会通过如下语句告

知上述对PM_QOS_CPU_DMA_LATENCY的性能要求取消:

static int viacam_streamon(struct file *filp, void *priv, enum v4l2_buf_type t)

{

pm_qos_remove_request(&cam->qos_request);

}

类似的设备驱动中申请QoS特性的例子还包括drivers/net/wireless/ipw2x00/ipw2100.c,

drivers/tty/serial/omap-serial.c,drivers/net/ethernet/intel/e1000e/netdev.c等。

应用程序则可以通过向/dev/cpu_dma_latency和/dev/network_latency这样的设备节点

写入值来发起QoS的性能请求。

8. CPU热插拔

Linux CPU热插拔的功能已经存在了相当长的时间,在Linux 3.8之后的内核一个小小的

改进就是CPU0也可以热插拔了。

一般来讲,在用户空间可以透过/sys/devices/system/cpu/cpun/online结点来操作一个CPU

的online和offline:

# echo 0 > /sys/devices/system/cpu/cpu3/online

CPU 3 is now offline

# echo 1 > /sys/devices/system/cpu/cpu3/online

透过“echo 0 > /sys/devices/system/cpu/cpu3/online”关闭CPU3的时候,CPU3上的进

程都会被迁移到其他的CPU上,保证这个拔除CPU3的过程中,系统仍然能正常运行。一旦

透过“echo 1 > /sys/devices/system/cpu/cpu3/online”再次开启CPU3,CPU3又可以参与系统

的负载均衡,分担系统中的任务。

在嵌入式系统中,CPU热插拔可以作为一种省电的方式,在系统负载小的时候,动态关

闭CPU,在系统负载增大的时候,再开启之前offline的CPU。目前各个芯片公司可能会根据

自身SoC的特点,对内核进行调整,来实现运行时“热插拔”。这里以Nvidia的Tegra3为例

展开。

Tegra3采用vSMP(variable Symmetric Multiprocessing)架构,共5个cortex-a9处理器,

其中4个为高性能设计的G核,1个为低功耗设计的LP核,如图19.7。

图19.7 Tegra3的架构

在系统运行过程中,Tegra3的Linux内核会根据CPU负载切换低功耗处理器和高功耗处

理器。除此之外,4个高性能ARM核心也会根据运行情况,动态借用Linux kernel支持的CPU

hotplug进行CPU的UP/DOWN操作。

用华硕EeePad运行高负载、低负载应用,通过dmesg查看内核消息也确实验证了多核

的热插拔以及G核和LP核之间的动态切换:

<4>[104626.426957] CPU1: Booted secondary processor

<7>[104627.427412] tegra CPU: force EDP limit 720000 kHz

<4>[104627.427670] CPU2: Booted secondary processor

<4>[104628.537005] stop_machine_cpu_stop cpu=0

<4>[104628.537017] stop_machine_cpu_stop cpu=2

<4>[104628.537059] stop_machine_cpu_stop cpu=1

<4>[104628.537702] __stop_cpus: wait_for_completion_timeout+

<4>[104628.537810] __stop_cpus: smp=0 ed=1 =0-

<5>[104628.537960] CPU1: clean shutdown

<4>[104630.537092] stop_machine_cpu_stop cpu=0

<4>[104630.537172] stop_machine_cpu_stop cpu=2

<4>[104630.537739] __stop_cpus: wait_for_completion_timeout+

<4>[104630.538060] __stop_cpus: smp=0 ed=1 =0-

<5>[104630.538203] CPU2: clean shutdown

<4>[104631.306984] tegra_watchdog_touch

高性能处理器和低功耗处理器切换:

<3>[104666.799152] LP=>G: prolog 22 us, switch 2129 us, epilog 24 us, total 2175 us

<3>[104667.807273] G=>LP: prolog 18 us, switch 157 us, epilog 25 us, total 200 us

<4>[104671.407008] tegra_watchdog_touch

<4>[104671.408816] nct1008_get_temp: ret temp=35C

<3>[104671.939060] LP=>G: prolog 17 us, switch 2127 us, epilog 22 us, total 2166 us

<3>[104672.938091] G=>LP: prolog 18 us, switch 156 us, epilog 24 us, total 198 us

运行过程中,我们会发现4个G core会动态热插拔,而4个G core和1个LP core之间,

会根据运行负载进行cluster切换。这一部分都是在内核里面实现,和tegra的cpufreq驱动

(DVFS驱动)紧密关联。相关代码可见于

/gitweb/?p=;a=tree;f=arch/arm/mach-tegra;h=e5d1ff2

;hb=rel-14r7

如何判断自己是什么core

每个core都可以通过调用is_lp_cluster()来判断当前执行CPU是LP还是G处理器:

static inline unsigned int is_lp_cluster(void)

{

unsigned int reg;

reg =readl(FLOW_CTRL_CLUSTER_CONTROL);

return (reg& 1); /* 0 == G, 1 == LP*/

}

即读FLOW_CTRL_CLUSTER_CONTROL寄存器判断出来自己是G core还是LP core。

G core和LP core cluster的切换时机

[场景1]何时从LP切换给G:当前执行于LPcluster,CPUFreq驱动判断出LP需要升频率

超过高值门限,即TEGRA_HP_UP:

case TEGRA_HP_UP:

if(is_lp_cluster() && !no_lp) {

if(!clk_set_parent(cpu_clk, cpu_g_clk)) {

hp_stats_update(CONFIG_NR_CPUS, false);

hp_stats_update(0, true);

/* catch-upwith governor target speed */

tegra_cpu_set_speed_cap(NULL);

}

[场景2]何时从G切换给LP:当前执行于Gcluster,CPUFreq驱动判断出某G core需要降

频率到小于低值门限,即TEGRA_HP_DOWN,且最慢的CPUID不小于nr_cpu_ids(实际上代

码逻辑跟踪等价于只有CPU0还活着):

case TEGRA_HP_DOWN:

cpu= tegra_get_slowest_cpu_n();

if(cpu < nr_cpu_ids) {

...

}else if (!is_lp_cluster() && !no_lp) {

if(!clk_set_parent(cpu_clk, cpu_lp_clk)) {

hp_stats_update(CONFIG_NR_CPUS, true);

hp_stats_update(0, false);

/* catch-upwith governor target speed */

tegra_cpu_set_speed_cap(NULL);

} else

queue_delayed_work(

hotplug_wq, &hotplug_work, down_delay);

}

break;

切换实际上就发生在clk_set_parent()更改CPU的父时钟里面,这部分代码写地比较丑,

1个函数完成n个功能,实际不仅切换了时钟,还切换了G和LP cluster:

clk_set_parent(cpu_clk, cpu_lp_clk) ->

tegra3_cpu_cmplx_clk_set_parent(structclk *c, struct clk *p) ->

tegra_cluster_control(unsigned int us, unsigned int flags) ->

tegra_cluster_switch_prolog()->

tegra_cluster_switch_epilog()

G core动态热插拔

何时进行G core的动态plug和unplug:

[场景3]当前执行于G cluster,CPUFreq驱动判断出某Gcore需要降频率到小于低值门限,

即TEGRA_HP_DOWN,且最慢的CPUID小于 nr_cpu_ids(实际上等价于还有2个或2个以上

的G core活着),关闭最慢的CPU,留意tegra_get_slowest_cpu_n()不会返回0,意味着CPU0

要么活着,要么切换给LP(对应场景2):

case TEGRA_HP_DOWN:

cpu= tegra_get_slowest_cpu_n();

if(cpu < nr_cpu_ids) {

up = false;

queue_delayed_work(

hotplug_wq,&hotplug_work, down_delay);

hp_stats_update(cpu, false);

}

[场景4]当前执行于G cluster,CPUFreq驱动判断出某Gcore需要设置频率大于高值门限,

即TEGRA_HP_UP,如果负载平衡状态为 TEGRA_CPU_SPEED_BALANCED,再开一个core;如

果状态为TEGRA_CPU_SPEED_SKEWED,则关一个core。 TEGRA_CPU_SPEED_BALANCED的含

义是当前所有Gcore要求的频率都高于最高频率的 50%,TEGRA_CPU_SPEED_SKEWED的含

义是当前至少有2个Gcore要求的频率低于门限的25%,即CPU 频率的要求在各个core间

有倾斜。

case TEGRA_HP_UP:

if (is_lp_cluster() && !no_lp) {

...

} else {

switch (tegra_cpu_speed_balance()) {

/* cpu speed is up and balanced - one more on-line */

case TEGRA_CPU_SPEED_BALANCED:

cpu =cpumask_next_zero(0, cpu_online_mask);

if (cpu

up =true;

hp_stats_update(cpu, true);

}

break;

/* cpu speed is up, but skewed - remove one core */

case TEGRA_CPU_SPEED_SKEWED:

cpu =tegra_get_slowest_cpu_n();

if (cpu < nr_cpu_ids) {

up =false;

hp_stats_update(cpu, false);

}

break;

/* cpu speed is up, butunder-utilized - do nothing */

case TEGRA_CPU_SPEED_BIASED:

default:

break;

}

}

上述代码中TEGRA_CPU_SPEED_BIASED路径的含义是有1个以上Gcore的频率低于最高

频率的50%但是未形成SKEWED条件,即只是“BIASED”,还没有达到“SKEWED”的程度,所以

暂时什么都不做。

目前,ARM和Linux社区都在从事关于架构下,CPU热插拔以及调度器方面

有针对性的改进。在架构中,将高性能功耗也较高的Cortex-A15和稍低性能功耗

低的Cortex-A7进行了结合,或者在64位下,进行Cortex-A57和Cortex-A53的组合,如图

19.8。

图19.8 ARM 架构

处理的设计旨在为适当的作业分配恰当的处理器。Cortex-A15 处理器是目前

已开发的性能最高的低功耗 ARM 处理器,而 Cortex-A7 处理器是目前已开发的最节能的

ARM 应用程序处理器。可以利用 Cortex-A15 处理器的性能来承担繁重的工作负载,而

Cortex-A7 可以最有效地处理智能手机的大部分工作负载。这些操作包括操作系统活动、用

户界面和其他持续运行、始终连接的任务。

三星在CES(国际消费电子展) 2013大会上发布了Exynos 5 Octa 八核移动处理器,这

款处理器也是采用结构的第一款CPU。

9. Suspend to RAM

Linux支持STANDBY、Suspend to RAM、Suspend to Disk等形式的待机,如图19.9。一般

的嵌入式产品仅仅实现Suspend to RAM(也简称s2ram,或常简称STR),即将系统的状态保

存于内存中,并将SDRAM置于自刷新状态,待用户按键等操作后重新恢复系统。少数嵌入

式Linux系统会实现Suspend to Disk(简称STD),它与Suspend to RAM的不同是s2ram并不

关机,STD则把系统的状态保持于磁盘,然后shutdown整个系统。

工作状态

standbyresume

suspendresume

Standby

浅度,唤

醒延迟小

Suspend

to RAM

HibernationRestore

中度,SDRAM自

刷新

深度,最

Suspend to

省电,唤

Disk

醒延迟大

(Hibernation)

图19.9 Linux待机模式

在Linux下,这些行为通常是由用户空间触发的,通过向

/sys/power/state写入“mem”

可开始Suspend to RAM的流程。当然,许多Linux产品会有一个按键,一按就进入

Suspend to RAM。这通常是由于这个按键对应的input设备驱动汇报了一个power相关

的input_event,用户空间的电源管理daemon进程收到这个事件后,再触发s2ram的。

当然,内核也有一个INPUT_APMPOWER驱动,位于drivers/input/apm-power.c,它可

以在内核级别侦听EV_PWR类事件,并透过

apm_queue_event(APM_USER_SUSPEND)

自动

引发s2ram:

static void system_power_event(unsigned int keycode)

{

switch (keycode) {

case KEY_SUSPEND:

apm_queue_event(APM_USER_SUSPEND);

pr_info("Requesting ");

break;

default:

break;

}

}

static void apmpower_event(struct input_handle *handle, unsigned int type,

unsigned int code, int value)

{

switch (type) {

case EV_PWR:

system_power_event(code);

break;

}

}

在Linux内核中,Suspend to RAM的suspend和resume大致流程如图19.10(牵涉的操

作包括同步文件系统、freeze进程、设备驱动suspend以及系统的suspend入口等)。

Suspend

Call Notifiers

Resume

Call Notifiers

Freeze TasksThaw Tasks

设备suspend

.prepare

设备resume

.complete

.

.suspend__early

.suspend_noirq

.resume_noirq

online

系统核心级offline系统核心级online

SoC平台级offlineSoC平台级online

图19.10 Linux Suspend To RAM流程

在Linux内核device_driver结构中,含有一个pm成员,它是一个dev_pm_ops结构体

指针,再该结构体中,封装了Suspend to RAM和Suspend to Disk所需要的callback函数,如

代码清单19.13。

代码清单19.13 dev_pm_ops结构体

1 struct dev_pm_ops {

2 int (*prepare)(struct device *dev);

3 void (*complete)(struct device *dev);

4 int (*suspend)(struct device *dev);

5 int (*resume)(struct device *dev);

6 int (*freeze)(struct device *dev);

7 int (*thaw)(struct device *dev);

8 int (*poweroff)(struct device *dev);

9 int (*restore)(struct device *dev);

10 int (*suspend_late)(struct device *dev);

11 int (*resume_early)(struct device *dev);

12 int (*freeze_late)(struct device *dev);

13 int (*thaw_early)(struct device *dev);

14 int (*poweroff_late)(struct device *dev);

15 int (*restore_early)(struct device *dev);

16 int (*suspend_noirq)(struct device *dev);

17 int (*resume_noirq)(struct device *dev);

18 int (*freeze_noirq)(struct device *dev);

19 int (*thaw_noirq)(struct device *dev);

20 int (*poweroff_noirq)(struct device *dev);

21 int (*restore_noirq)(struct device *dev);

22 int (*runtime_suspend)(struct device *dev);

23 int (*runtime_resume)(struct device *dev);

24 int (*runtime_idle)(struct device *dev);

25 };

图19.10实际上也给出了Suspend to RAM的时候这些PM callback函数的调用时机。

目前比较推荐的做法是在platform_driver、i2c_driver和spi_driver等xxx_driver结构体实

例的driver成员中,以上述结构体的形式封装PM callback函数,赋值到driver的pm字段。

如代码清单19.14的第50行,在drivers/spi/ spi-s3c64xx.c中,platform_driver中的pm成员

被赋值。

代码清单19.14 设备驱动中的pm成员

01 #ifdef CONFIG_PM_SLEEP

02 static int s3c64xx_spi_suspend(struct device *dev)

03 {

04 struct spi_master *master = dev_get_drvdata(dev);

05 struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);

06

07 int ret = spi_master_suspend(master);

08 if (ret)

09 return ret;

10

11 if (!pm_runtime_suspended(dev)) {

12 clk_disable_unprepare(sdd->clk);

13 clk_disable_unprepare(sdd->src_clk);

14 }

15

16 sdd->cur_speed = 0; /* Output Clock is stopped */

17

18 return 0;

19 }

20

21 static int s3c64xx_spi_resume(struct device *dev)

22 {

23 struct spi_master *master = dev_get_drvdata(dev);

24 struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);

25 struct s3c64xx_spi_info *sci = sdd->cntrlr_info;

26

27 if (sci->cfg_gpio)

28 sci->cfg_gpio();

29

30 if (!pm_runtime_suspended(dev)) {

31 clk_prepare_enable(sdd->src_clk);

32 clk_prepare_enable(sdd->clk);

33 }

34

35 s3c64xx_spi_hwinit(sdd, sdd->port_id);

36

37 return spi_master_resume(master);

38 }

39 #endif /* CONFIG_PM_SLEEP */

40

41 static const struct dev_pm_ops s3c64xx_spi_pm = {

42 SET_SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume)

43 SET_RUNTIME_PM_OPS(s3c64xx_spi_runtime_suspend,

44 s3c64xx_spi_runtime_resume, NULL)

45 };

46

47 static struct platform_driver s3c64xx_spi_driver = {

48 .driver = {

49 .name = "s3c64xx-spi",

50 .pm = &s3c64xx_spi_pm,

51 .of_match_table = of_match_ptr(s3c64xx_spi_dt_match),

52 },

53 .probe = s3c64xx_spi_probe,

54 .remove = s3c64xx_spi_remove,

55 .id_table = s3c64xx_spi_driver_ids,

56 };

s3c64xx_spi_suspend()完成了时钟的disable,s3c64xx_spi_resume()则完成了硬件的重新

初始化、时钟的enable等工作。第42行的SET_SYSTEM_SLEEP_PM_OPS是一个快捷宏,它

完成suspend、resume等成员函数的赋值:

#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)

.suspend = suspend_fn,

.resume = resume_fn,

.freeze = suspend_fn,

.thaw = resume_fn,

.poweroff = suspend_fn,

.restore = resume_fn,

除了上述推行的做法以外,在platform_driver、i2c_driver、spi_driver等xxx_driver结构

体本身仍然保留了过时(legacy)的suspend、resume入口函数(目前不再推荐使用过时的

接口,而是推荐赋值xxx_driver中的driver的pm成员),譬如代码清单19.15给出的

platform_driver就包含了legacy的suspend、resume等。

代码清单19.15设备驱动中的legacy PM成员函数

1

2

3

4

5

6

7

8

9

struct platform_driver {

int (*probe)(struct platform_device *);

int (*remove)(struct platform_device *);

void (*shutdown)(struct platform_device *);

int (*suspend)(struct platform_device *, pm_message_t state);

int (*resume)(struct platform_device *);

struct device_driver driver;

const struct platform_device_id *id_table;

};

在Linux的核心层,实际上是优先选择执行xxx_d()成员函数,在前

者不存在的情况下,执行过时的xxx_d(),如platform_pm_suspend()的逻辑如代

码清单19.16。

代码清单19.16 驱动核心层寻找PM callback的顺序

01 int platform_pm_suspend(struct device *dev)

02 {

03 struct device_driver *drv = dev->driver;

04 int ret = 0;

05

06 if (!drv)

07 return 0;

08

09 if (drv->pm) {

10 if (drv->pm->suspend)

11 ret = drv->pm->suspend(dev);

12 } else {

13 ret = platform_legacy_suspend(dev, PMSG_SUSPEND);

14 }

15

16 return ret;

17 }

一般来讲,在设备驱动的suspend入口函数中,会关闭设备、关闭该设备的时钟输入、

甚至是关闭设备的电源,resume时则完成相反的操作。在Suspend to RAM的suspend和

resume过程中,系统恢复后要求所有设备的驱动都工作正常。为了调试这个过程,可以使

能内核的PM_DEBUG选项,如果想在suspend和resume的过程中,看到内核的打印信息以

关注具体的详细流程,可以在Bootloader传递给内核的bootargs中设置标志

no_console_suspend。

在将Linux移植到一个新的ARM SoC的过程中,最终系统suspend的入口需由芯片供应

商在相应的arch/arm/mach-xxx中实现platform_suspend_ops的成员函数,一般主要实现其

中的enter和valid成员,并将整个platform_suspend_ops结构体通过内核通用API

suspend_set_ops()注册进系统,如arch/arm/mach-prima2/pm.c中prima2 SoC进suspend流

程的逻辑如代码清单19.17。

代码清单19.17 系统Suspend to RAM的SoC级代码

01 static int sirfsoc_pm_enter(suspend_state_t state)

02 {

03 switch (state) {

04 case PM_SUSPEND_MEM:

05 sirfsoc_pre_suspend_power_off();

06

07 outer_flush_all();

08 outer_disable();

09 /* go zzz */

10 cpu_suspend(0, sirfsoc_finish_suspend);

11 outer_resume();

12 break;

13 default:

14 return -EINVAL;

15 }

16 return 0;

17 }

18

19 static const struct platform_suspend_ops sirfsoc_pm_ops = {

20 .enter = sirfsoc_pm_enter,

21 .valid = suspend_valid_only_mem,

22 };

23

24 int __init sirfsoc_pm_init(void)

25 {

26 suspend_set_ops(&sirfsoc_pm_ops);

27 return 0;

28 }

上述代码中的第5行的sirfsoc_pre_suspend_power_off()的实现如代码清单19.18,它会

将系统resume回来后重新开始执行的物理地址存入SoC相关的寄存器中。本例中对应的寄

存器为SIRFSOC_PWRC_SCRATCH_PAD1,并向该寄存器写入了virt_to_phys(cpu_resume)。系

统重新resume中,会执行到cpu_resume这段汇编,并进行设置唤醒源等操作。

代码清单19.18 SoC设置resume唤醒回来时的执行地址

01

02

03

04

05

06

07

08

09

10

11

12

13

static int sirfsoc_pre_suspend_power_off(void)

{

u32 wakeup_entry = virt_to_phys(cpu_resume);

sirfsoc_rtc_iobrg_writel(wakeup_entry, sirfsoc_pwrc_base +

SIRFSOC_PWRC_SCRATCH_PAD1);

sirfsoc_set_wakeup_source();

sirfsoc_set_sleep_mode(SIRFSOC_DEEP_SLEEP_MODE);

return 0;

}

而cpu_suspend(0, sirfsoc_finish_suspend)以及其中调用的SoC相关的汇编实现的函数

sirfsoc_finish_suspend()真正完成最后的待机以及将系统置于深度睡眠,并置SDRAM于自刷

新状态的过程,具体的代码高度依赖于特定的芯片,其实现一般是一段汇编。

10. Runtime PM

前文给出的dev_pm_ops结构体中,有3个以runtime开头的成员函数:

runtime_suspend()、runtime_resume()和 runtime_idle (),它们辅助设备完成运行时的电源管

理:

struct dev_pm_ops {

...

int (*runtime_suspend)(struct device *dev);

int (*runtime_resume)(struct device *dev);

int (*runtime_idle)(struct device *dev);

...

};

运行时的PM与前文描述的系统级Suspend to RAM时候的PM不太一样,它是针对单个

设备。指系统在非睡眠状态的情况下,某个设备在空闲时可以进入runtime suspend状态,

而非空闲时执行runtime resume使得设备进入正常工作状态,这样这个设备在运行时会省电。

Linux Runtime PM最早是在 2.6.32内核被merge的。

Linux提供一系列API以便于设备可以声明自己的运行时PM状态:

int pm_runtime_suspend(struct device *dev);

引发设备的suspend,执行相关的runtime_suspend函数。

int pm_schedule_suspend(struct device *dev, unsigned int delay);

“调度”设备的suspend,延迟delay毫秒后将suspend工作挂入pm_wq等待队列,结

果等价于delay毫秒后执行相关的runtime_suspend函数。

int pm_request_autosuspend(struct device *dev);

“调度”设备的suspend,autosuspend的延迟到后,suspend的工作项目被自动放入队

列。

int pm_runtime_resume(struct device *dev);

引发设备的resume,执行相关的runtime_resume函数。

int pm_request_resume(struct device *dev);

发起一个设备resume的请求,该请求也是挂入pm_wq等待队列。

int pm_runtime_idle(struct device *dev);

引发设备的idle,执行相关的runtime_idle函数。

int pm_request_idle(struct device *dev);

发起一个设备idle的请求,该请求也是挂入pm_wq等待队列。

void pm_runtime_enable(struct device *dev);

使能设备的runtime PM支持。

int pm_runtime_disable(struct device *dev);

禁止设备的runtime PM支持。

int pm_runtime_get(struct device *dev);

int pm_runtime_get_sync(struct device *dev);

增加设备的引用计数(usage_count),类似于clk_get(),会间接引发设备的

runtime_resume。

int pm_runtime_put(struct device *dev);

int pm_runtime_put_sync(struct device *dev)

减小设备的引用计数,类似于clk_put(),会间接引发设备的runtime_idle。

我们可以这样简单地理解Linux Runtime PM的机制,每个设备(总线的控制器自身也属

于一个设备)都有引用计数usage_count和活跃子设备(active children,子设备的意思就是

该级总线上挂的设备)计数child_count,当两个计数都为0的时候,就进入空闲状态,调用

pm_request_idle(dev)。当设备进入空闲状态,pm_request_idle(dev)对应的PM core调

用的并不一定直接是设备驱动的runtime_suspend(),它实际上多数情况下是调用该设备对应

的bus_type的runtime_idle()。根据内核的代码逻辑:

static pm_callback_t __rpm_get_callback(struct device *dev, size_t cb_offset)

{

pm_callback_t cb;

const struct dev_pm_ops *ops;

if (dev->pm_domain)

ops = &dev->pm_domain->ops;

else if (dev->type && dev->type->pm)

ops = dev->type->pm;

else if (dev->class && dev->class->pm)

ops = dev->class->pm;

else if (dev->bus && dev->bus->pm)

ops = dev->bus->pm;

else

ops = NULL;

if (ops)

cb = *(pm_callback_t *)((void *)ops + cb_offset);

else

cb = NULL;

if (!cb && dev->driver && dev->driver->pm)

cb = *(pm_callback_t *)((void *)dev->driver->pm + cb_offset);

return cb;

}

bus_type级的callback实际上可以被pm_domain、type、class覆盖掉。这些都统称为子

系统。bus_type等子系统级别的runtime_idle()行为完全由相应的总线类型、设备分类和

pm_domain因素决定,但是一般的行为是子系统级别的runtime_idle()会调度设备的driver

的runtime_suspend()被调用。

在具体的设备驱动中,一般的用法则是在设备驱动probe()时运行pm_runtime_enable()

使能runtime PM支持,在运行过程中动态地执行“pm_runtime_get_xxx() -> 做工作 ->

pm_runtime_put_xxx()”的序列。如代码清单19.19的drivers/watchdog/omap_wdt.c OMAP

的看门狗驱动。在omap_wdt_start()中启动了pm_runtime_get_sync(),而在omap_wdt_stop()

中调用了pm_runtime_put_sync()。

代码清单19.19 运行时PM的pm_runtime_get和pm_runtime_put

01 static int omap_wdt_start(struct watchdog_device *wdog)

02 {

03 struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);

04 void __iomem *base = wdev->base;

05

06 mutex_lock(&wdev->lock);

07

08 wdev->omap_wdt_users = true;

09

10 pm_runtime_get_sync(wdev->dev);

11

12 /* initialize prescaler */

13 while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x01)

14 cpu_relax();

15 ...

16 mutex_unlock(&wdev->lock);

17

18 return 0;

19 }

20 static int omap_wdt_stop(struct watchdog_device *wdog)

21 {

22 struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);

23

24 mutex_lock(&wdev->lock);

25 omap_wdt_disable(wdev);

26 pm_runtime_put_sync(wdev->dev);

27 wdev->omap_wdt_users = false;

28 mutex_unlock(&wdev->lock);

29 return 0;

30 }

31

32 static const struct watchdog_ops omap_wdt_ops = {

33 .owner = THIS_MODULE,

34 .start = omap_wdt_start,

35 .stop = omap_wdt_stop,

36 .ping = omap_wdt_ping,

37 .set_timeout = omap_wdt_set_timeout,

38 };

上述代码第10行的pm_runtime_get_sync(wdev->dev)告诉内核要开始用watchdog这个

设备了,如果watchdog设备已经进入省电模式(之前引用计数为0、且执行了runtime

suspend),会导致该设备的runtime resume;第26行告诉内核,不用这个设备了,如果引

用计数变为0且活跃子设备为0,则导致该watchdog设备的runtime suspend。

在某些设备驱动中,直接使用上述引用计数的方法进行suspend、idle和resume不一定

是合适的,因为suspend状态的进入和恢复需要一些时间,如果设备不会在suspend保留一

定的时间,频繁进出suspend则反而带来新的开销。所以我们可根据情况决定只有设备在空

闲了一段时间后才进入suspend(一般来说,一个一段时间没有被使用的设备,还会有一段

时间不会被使用),基于此,一些设备驱动也常常使用autosuspend模式进行编程。

在执行操作的时候声明pm_runtime_get(),操作完成后执行pm_runtime_mark_last_busy()

和pm_runtime_put_autosuspend(),一旦autospend的delay到期,且设备的使用计数为0,

则引发相关runtime_suspend()入口函数的调用。一个典型用法如代码清单19.20。

代码清单19.20 运行时PM的autospend

01 foo_read_or_write(struct foo_priv *foo, void *data)

02 {

03 lock(&foo->private_lock);

04 add_request_to_io_queue(foo, data);

05 if (foo->num_pending_requests++ == 0)

06 pm_runtime_get(&foo->dev);

07 if (!foo->is_suspended)

08 foo_process_next_request(foo);

09 unlock(&foo->private_lock);

10 }

11

12 foo_io_completion(struct foo_priv *foo, void *req)

13 {

14 lock(&foo->private_lock);

15 if (--foo->num_pending_requests == 0) {

16 pm_runtime_mark_last_busy(&foo->dev);

17 pm_runtime_put_autosuspend(&foo->dev);

18 } else {

19 foo_process_next_request(foo);

20 }

21 unlock(&foo->private_lock);

22 /* Send req result back to the user ... */

23 }

上述代码第6行开始进行I/O传输了,所以运行了pm_runtime_get(),之后当I/O传输结

束的时候,第16~17行向内核告知该设备最后的busy时刻,并执行了

pm_runtime_put_autosuspend()。

“autosuspend”的delay可以通过void pm_runtime_set_autosuspend_delay(struct device

*dev, int delay)这个API来设定的。

设备驱动PM成员的runtime_suspend一般完成保存上下文、切到省电模式的工作,而

runtime_resume一般完成对硬件上电、恢复上下文的工作。代码清单19.21给出了一个

drivers/spi/spi-pl022.c的案例。

代码清单19.21 运行时PM的runtime_suspend/resume案例

01 #ifdef CONFIG_PM

02 static int pl022_runtime_suspend(struct device *dev)

03 {

04 struct pl022 *pl022 = dev_get_drvdata(dev);

05

06 clk_disable_unprepare(pl022->clk);

07 pinctrl_pm_select_idle_state(dev);

08

09 return 0;

10 }

11

12 static int pl022_runtime_resume(struct device *dev)

13 {

14 struct pl022 *pl022 = dev_get_drvdata(dev);

15

16 pinctrl_pm_select_default_state(dev);

17 clk_prepare_enable(pl022->clk);

18

19 return 0;

20 }

21 #endif

22

23 static const struct dev_pm_ops pl022_dev_pm_ops = {

24 SET_SYSTEM_SLEEP_PM_OPS(pl022_suspend, pl022_resume)

25 SET_RUNTIME_PM_OPS(pl022_runtime_suspend, pl022_runtime_resume, NULL)

26 };

第25行的SET_RUNTIME_PM_OPS是一个快捷宏,它完成了runtime_suspend、

runtime_resume的赋值动作,其定义如下:

#define SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn)

.runtime_suspend = suspend_fn,

.runtime_resume = resume_fn,

.runtime_idle = idle_fn,

其实,除了SET_RUNTIME_PM_OPS和前文介绍的SET_SYSTEM_SLEEP_PM_OPS,在

include/linux/pm.h还定义了SIMPLE_DEV_PM_OPS、UNIVERSAL_DEV_PM_OPS等更快捷的宏:

#define SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn)

const struct dev_pm_ops name = {

SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)

}

#define UNIVERSAL_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn)

const struct dev_pm_ops name = {

SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)

SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn)

}

在内核里充斥着这些宏的使用例子。我们从UNIVERSAL_DEV_PM_OPS这个宏的定义可以

看出,它针对的是Suspend to RAM和runtime PM行为一致的场景。

11. 总结

Linux内核的PM框架涉及众多组件,弄清楚这些组件之间的依赖关系,在合适的着眼点

上进行优化,采用正确的方法进行PM的编程,对改善代码的质量、辅助功耗和性能测试都

有极大的好处。

另外,在实际工程中,尤其是从事消费电子的邻域,可能有超过半数的bug都属于电源

管理。这个时候,电源管理的很多工作就是在搞定鲁棒性和健壮性,可以说,很多时候就是

个体力活了,需要足够的耐性。