linux cpu功耗软件,Linux CPU core的电源管理(3)_cpu ops

Linux CPU core的电源管理(3)_cpu ops

作者:wowo 发布于:2015-7-17 22:15

分类:电源管理子系统

1. 前言

由“ARMv8-a架构简介”中有关的介绍可知,ARMv8(包括ARMv7的一些扩展)引入了Virtualization、Security等概念。在这些概念之下,传统的CPU boot、shutdown、reset、suspend/resume等操作,不再那么简单和单纯。因此,ARM将这些底层操作抽象为一些operations,在以统一的方式向上层软件提供API的同时,可以根据不同的场景,有不同的实现。这就是本文要描述的cpu ops。

注1:由“Linux CPU core的电源管理(1)_概述”的描述可知,cpu ops属于arch-dependent的部分,本文基于ARM64平台。

2. cpu ops的功能

要弄明白cpu ops真正意义,我们需要回忆两个知识点:“Linux CPU core的电源管理(2)_cpu topology”中描述的ARM CPU的拓扑结构,主要包括Cluster、Core、Thread等概念;“ARMv8-a架构简介”中提到的Exception level的概念,如下:

849279624940282aa30ed2318e2445a5.gif

基于上面两个知识点,我们来理解一下cpu ops现实意义,以及它所提供的功能。

2.1 secondary CPU boot

ARM64是一个SMP系统,可能包含多个cluster、多核CPU core(注2:目前为止,ARM没有明显提出多线程的概念)。在SMP系统中,Linux kernel会在一个CPU(primary CPU)上完成启动操作。primary CPU启动完成后,再启动其它的CPU(secondary CPUs),这称作secondary CPU boot。

继续secondary CPU boot的话题之前,我们先理解一下“boot”的意义。接触过单片机的人都知道,所谓的CPU boot,就是让CPU执行(取指译码/执行)代码(这里为linux kernel),有如下的表现方式: 1)所有的CPU,一上电,就会从固定的地址(一般为0)取指、执行。

2)最简单的嵌入式系统,会将代码保存在ROM中,并把ROM映射到0地址,因此CPU上电后,会自动执行代码。

3)稍微复杂一点的系统,CPU(或SOC)中会集成一个ROM,ROM上有CPU厂商在出厂时固化的代码,这些代码会进行一些必要的初始化后,将CPU跳转到其它地址(例如0x20000000),这些地址一般是RAM或者NOR flash,用户代码可以存放在这些位置。

4)更复杂的系统,例如基于ARM64的SOC,可能包含多个CPU core,不同的CPU core可能有着不同的power domain,因而有可能单独上电。

5)再复杂一些,例如实现了virtualization功能的ARM64,linux kernel(上面图片中的Guest OS)运行在虚拟CPU上,此时的boot,需要依赖下层的Hypervisor。

6)等等。

因此,不同的SOC,可能有着不同的secondary CPU boot的方法,OS(如linux kernel)必须能够应付这种差异。至于应付的方法,无非就是封装、抽象:将boot相关的功能抽象为标准接口,上层软件(arch/arm64/kernel/smp.c)以统一的方式调用,下层软件(arch/arm64/kernel/psci.c等)根据具体的架构,实现这些功能。

2.2 CPU hotplug

hotplug功能,是在处理性能需求不高的情况下,从物理上关闭不需要的CPU core,并在需要的时候,将它们切换为online状态的一种手段。

和cpuidle类似,cpu hotplug也是根据系统负荷,动态调整处理器性能,从而达到节省功耗的目的。其区别在于: 处于idle状态的CPU,对调度器来说是可见的,换句话说,调度器并不知道某个CPU是否处于idle状态,因此不需要对它们做特殊处理。

而处于un-hotplugged状态CPU,对调度器是不可见,因此调度器必须做一些额外的处理,包括:负荷较低时,主动移除CPU,并将该CPU上的中断等资源迁移到其它CPU上,同时进行必要的负载均衡;反之亦然。

同样的道理,不同的架构,CPU hotplug的方式也不一样,嗯,封装抽象嘛!

2.3 idle、shutdown、reset、suspend/resume、big·LITTLE等电源管理操作

同理,不同的架构,这些电源管理操作的实现方式也不一样,例如: 1)假设一个SOC,有多个cluster、多个core,且这些cluster和core都可以单独供电。那么shutdown操作就比较复杂,例如,每一个core掉电后,都要检查该core的sibling core是否都已掉电,如果是,则关闭cluster的供电。

2)再假如,在实现了virtualization的系统上,某一个Guest OS要求reset、suspend系统,必须经过Hypervisor处理,否则就会影响其它OS的正常工作。

3)等等

接着封装抽象吧。

最后总结一下,cpu ops存在的本质意义是什么?我的理解是: 在SMP、Virtualization、Security等大背景下,OS对CPU core及其power等资源的使用,可能存在独占、共享等多种方式。这就要求OS能抽象出一个友好、实用的框架,去管理、使用这些资源。具体请参考后面的描述。

3. ARM64 cpu ops在kernel中的实现

3.1 struct cpu_operations结构

对ARM64平台来说,kernel使用struct cpu_operations来抽象cpu ops,如下:

1: /* arch/arm64/include/asm/cpu_ops.h */

2: /**

3: * struct cpu_operations - Callback operations for hotplugging CPUs.

4: *

5: * @name: Name of the property as appears in a devicetree cpu node's

6: * enable-method property.

7: * @cpu_init: Reads any data necessary for a specific enable-method from the

8: * devicetree, for a given cpu node and proposed logical id.

9: * @cpu_init_idle: Reads any data necessary to initialize CPU idle states from

10: * devicetree, for a given cpu node and proposed logical id.

11: * @cpu_prepare: Early one-time preparation step for a cpu. If there is a

12: * mechanism for doing so, tests whether it is possible to boot

13: * the given CPU.

14: * @cpu_boot: Boots a cpu into the kernel.

15: * @cpu_postboot: Optionally, perform any post-boot cleanup or necesary

16: * synchronisation. Called from the cpu being booted.

17: * @cpu_disable: Prepares a cpu to die. May fail for some mechanism-specific

18: * reason, which will cause the hot unplug to be aborted. Called

19: * from the cpu to be killed.

20: * @cpu_die: Makes a cpu leave the kernel. Must not fail. Called from the

21: * cpu being killed.

22: * @cpu_kill: Ensures a cpu has left the kernel. Called from another cpu.

23: * @cpu_suspend: Suspends a cpu and saves the required context. May fail owing

24: * to wrong parameters or error conditions. Called from the

25: * CPU being suspended. Must be called with IRQs disabled.

26: */

27: struct cpu_operations {

28: const char *name;

29: int (*cpu_init)(struct device_node *, unsigned int);

30: int (*cpu_init_idle)(struct device_node *, unsigned int);

31: int (*cpu_prepare)(unsigned int);

32: int (*cpu_boot)(unsigned int);

33: void (*cpu_postboot)(void);

34: #ifdef CONFIG_HOTPLUG_CPU

35: int (*cpu_disable)(unsigned int cpu);

36: void (*cpu_die)(unsigned int cpu);

37: int (*cpu_kill)(unsigned int cpu);

38: #endif

39: #ifdef CONFIG_ARM64_CPU_SUSPEND

40: int (*cpu_suspend)(unsigned long);

41: #endif

42: };

该接口提供了一些CPU操作相关的回调函数,由底层代码(可以称作cpu ops driver)根据实际情况实现,并由ARM64的SMP模块(可参考“Linux CPU core的电源管理(1)_概述”中的相关描述)调用:

name,该operations的名字,需要唯一。

cpu_init,该cpu operations的初始化接口,会在SMP初始化时调用,cpu ops driver可以在这个接口中,完成一些必须的初始化动作,如读取寄存器值、从DTS中获取配置等。

cpu_init_idle,CPU idle有关的初始化接口,会由cpuidle driver在初始化时调用(可参考“Linux cpuidle framework(3)_ARM64 generic CPU idle driver”)。cpu ops driver可以在这个接口中实现和idle有关的初始化操作,我们会在后续将PSCI ops的时候再介绍。

cpu_prepare、cpu_boot、cpu_postboot,CPU boot有关的接口,分别在boot前、boot时、boot后调用。

如果使能了hotplug功能,除了boot接口之外,需要额外实现用于关闭CPU(和 boot相对)的接口,包括cpu_disable、cpu_die、cpu_kill,后面介绍hotplug流程中,会重点介绍这几个接口。

如果使能了CPU suspend功能,则由cpu_suspend完成相应的suspend动作。

3.2 cpu ops driver

kernel中ARM64的核心代码(arch/arm64/kernel)通过struct cpu_operations结构规定了cpu ops的基本框架,不同的硬件实现,则可使用不同的operations。针对ARM64,kernel提供了两种可选的方法,smp spin table和psci,如下:

1: /* arch/arm64/kernel/cpu_ops.c */

2:

3: static const struct cpu_operations *supported_cpu_ops[] __initconst = {

4: #ifdef CONFIG_SMP

5: &smp_spin_table_ops,

6: #endif

7: &cpu_psci_ops,

8: NULL,

9: };

具体使用哪一个operation,是通过DTS指定的,DTS格式如下:

1: /* spin-table: arch/arm64/boot/dts/apm-storm.dtsi */

2: cpus {

3: ...

4: cpu@000 {

5: ...

6: enable-method = "spin-table";

7: cpu-release-addr = <0x1 0x0000fff8>;

8: };

9: ...

10: };

11:

12: /* or psci: arch/arm64/boot/dts/thunder-88xx.dtsi */

13: cpus {

14: ...

15: cpu@000 {

16: ...

17: enable-method = "psci";

18: };

19: ...

20: };

即,在每一个cpu子节点中,使用“enable-method”指定是使用“spin-table”还是“psci”。

系统初始化的时候,会根据DTS信息,获取使用的operations(setup_arch-->cpu_read_bootcpu_ops-->cpu_read_ops),最终保存在一个operation数组(每个CPU一个)中,供SMP(arch/arm64/kernel/smp.c)使用,如下:

1: /* arch/arm64/kernel/cpu_ops.c */

2: const struct cpu_operations *cpu_ops[NR_CPUS];

相关的流程比较简单,具体可参考代码。

4. spin-table operations

spin-table是一种简单的、可支持SMP操作的cpu operations,在“arch/arm64/kernel/smp_spin_table.c”中实现:

1: const struct cpu_operations smp_spin_table_ops = {

2: .name = "spin-table",

3: .cpu_init = smp_spin_table_cpu_init,

4: .cpu_prepare = smp_spin_table_cpu_prepare,

5: .cpu_boot = smp_spin_table_cpu_boot,

6: };

由上面的实现可知,spin-table operations只支持cpu_init、cpu_prepare和cpu_boot三个回调函数,因此,它只能实现secondary cpu boot的功能,其他功能,如cpu hotplug、cpuidle、电源管理等,均不支持。

4.1 secondary cpu boot流程

它的名字(spin)就可以看出secondary cpu boot的实现逻辑,即:

SOC上电后,每个CPU都启动(开始执行ROM代码),除了boot CPU正常执行外,其它的secondary CPUs,都等待在一个地方(例如使用WFE指令),直到boot CPU启动完成后,再发消息通知其它CPU执行。示意图如下。

aa7b069318c5c34fed346e661bb3f526.gif

1)系统上电后,每个CPU都开始执行bootloader(这里以u-boot为例),boot CPU在进行必要的初始化后,继续执行。其它CPU(secondary CPU),则等待在一个地方,直到一个地址(由CPU_RELEASE_ADDR指定,因此该地址就为spin table)变为非零值,就会跳到改地址所指定的位置继续执行。为了节省power,这里的等待可以通过WFE指令,使CPU进入idle状态。

2)boot CPU进入kernel,在初始化的过程中,依次执行.cpu_init(),.cpu_prepare(),.cpu_boot()三个回调函数,boot secondary CPUs。以本节所描述的spin-table operations为例,这三个回调函数分别完成如下功能:

1: static int smp_spin_table_cpu_init(struct device_node *dn, unsigned int cpu)

2: {

3: /*

4: * Determine the address from which the CPU is polling.

5: */

6: if (of_property_read_u64(dn, "cpu-release-addr",

7: &cpu_release_addr[cpu])) {

8: pr_err("CPU %d: missing or invalid cpu-release-addr property\n",

9: cpu);

10:

11: return -1;

12: }

13:

14: return 0;

15: }

.cpu_init()接口,负责从DTS中解析出每个secondary CPU的“cpu-release-addr”,其实就是上面所说的CPU_RELEASE_ADDR。

1: static int smp_spin_table_cpu_prepare(unsigned int cpu)

2: {

3: __le64 __iomem *release_addr;

4:

5: if (!cpu_release_addr[cpu])

6: return -ENODEV;

7:

8: /*

9: * The cpu-release-addr may or may not be inside the linear mapping.

10: * As ioremap_cache will either give us a new mapping or reuse the

11: * existing linear mapping, we can use it to cover both cases. In

12: * either case the memory will be MT_NORMAL.

13: */

14: release_addr = ioremap_cache(cpu_release_addr[cpu],

15: sizeof(*release_addr));

16: if (!release_addr)

17: return -ENOMEM;

18:

19: /*

20: * We write the release address as LE regardless of the native

21: * endianess of the kernel. Therefore, any boot-loaders that

22: * read this address need to convert this address to the

23: * boot-loader's endianess before jumping. This is mandated by

24: * the boot protocol.

25: */

26: writeq_relaxed(__pa(secondary_holding_pen), release_addr);

27: __flush_dcache_area((__force void *)release_addr,

28: sizeof(*release_addr));

29:

30: /*

31: * Send an event to wake up the secondary CPU.

32: */

33: sev();

34:

35: iounmap(release_addr);

36:

37: return 0;

38: }

.cpu_prepare()接口,将secondary_holding_pen的物理地址写入“cpu-release-addr”,并调用sev()指令,唤醒那些处于WFE状态的secondary CPUs,它们开始执行secondary_holding_pen函数。

.cpu_boot()的功能后面再介绍。

3)secondary CPUs开始执行secondary_holding_pen,该接口的主要共功能是:

1: /* arch/arm64/kernel/head.S */

2:

3: .align 3

4: 1: .quad .

5: .quad secondary_holding_pen_release

6:

7: /*

8: * This provides a "holding pen" for platforms to hold all secondary

9: * cores are held until we're ready for them to initialise.

10: */

11: ENTRY(secondary_holding_pen)

12: bl el2_setup // Drop to EL1, w20=cpu_boot_mode

13: bl __calc_phys_offset // x24=PHYS_OFFSET, x28=PHYS_OFFSET-PAGE_OFFSET

14: bl set_cpu_boot_mode_flag

15: mrs x0, mpidr_el1

16: ldr x1, =MPIDR_HWID_BITMASK

17: and x0, x0, x1

18: adr x1, 1b

19: ldp x2, x3, [x1]

20: sub x1, x1, x2

21: add x3, x3, x1

22: ldr x4, [x3]

23: pen: cmp x4, x0

24: b.eq secondary_startup

25: wfe

26: b pen

27: ENDPROC(secondary_holding_pen)

首先,执行secondary_holding_pen,就意味着secondary CPUs们已经进入了kernel。

在这里,CPU继续等到,直到secondary_holding_pen_release变量和该CPU的ID(由寄存器mpidr_el1读取)相同,才继续执行后续操作(secondary_startup)。

那什么时候secondary_startup变量和CPU ID相同呢?看一下.cpu_boot()就知道了。

1: /*

2: * Write secondary_holding_pen_release in a way that is guaranteed to be

3: * visible to all observers, irrespective of whether they're taking part

4: * in coherency or not. This is necessary for the hotplug code to work

5: * reliably.

6: */

7: static void write_pen_release(u64 val)

8: {

9: void *start = (void *)&secondary_holding_pen_release;

10: unsigned long size = sizeof(secondary_holding_pen_release);

11:

12: secondary_holding_pen_release = val;

13: __flush_dcache_area(start, size);

14: }

15:

16: static int smp_spin_table_cpu_boot(unsigned int cpu)

17: {

18: /*

19: * Update the pen release flag.

20: */

21: write_pen_release(cpu_logical_map(cpu));

22:

23: /*

24: * Send an event, causing the secondaries to read pen_release.

25: */

26: sev();

27:

28: return 0;

29: }

原来是.cpu_boot()向secondary_holding_pen_release中写了自己的CPU ID。为什么要这样呢?

.cpu_prepare()只是尽快让secondary CPUs进入kernel(以便释放bootloader占用的资源)。但是,要继续执行,必须在boot CPU执行完必要的初始化之后,那就等待咯。

注3:smp spin table operations是非常简单的一种cpu ops,不支持其它的电源管理功能,不支持cpu hotplug,不支持virtualization和security。因此,在ARMv8处理器中,几乎很少使用它作为CPU操作的底层实现。

5. psci operations

PSCI是Power State Coordination Interface的简称,鉴于它的复杂度和篇幅问题,我们放到另一篇文章中单独描述。本文就到此结束了。

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。c6a6308114f401be7df747ae46f2b4db.png

评论:

伊斯科明

2019-10-17 20:19

大神什么时候讲一下PSCI的东西呢?

lutingleo

2016-07-07 11:16

请问psci跟硬件相关么?Coretex-A17 双核能用吗?

2016-07-07 15:03

@lutingleo:理论上说,PSCI是一个Firmware,位于Hypervisor。能不能用,要看一下Coretex-A17的结构,以及CPU厂商有没有提供这个Firmware。

bigchris

2016-10-13 15:42

@wowo:PSCI是处于EL3,Secure monitor,Hypervisor在移动芯片上基本不会用到,据ARM说服务器上vmm会位于Hypervisor。

2016-10-13 18:10

@bigchris:多谢提醒,学习了:-)

lutingleo

2016-07-07 11:14

请问psci会跟硬件相关么?还是说随便一个ARM SMP都可以使用,比如Cortex-A17 双核

没完没了

2017-12-27 13:50

@lutingleo:psci的实现是在secure monitor里面的atf软件包里面,目前arm应该主要实现了V8架构相关的软件设计。 据说正在开发V7架构的

zyq1228

2016-05-11 01:08

@wowo, 还有个问题请教一下哦。

关于cpu_ops, 你的文中提到了spin_table和psci,不知道你有没有听过还有一个叫mcpm(Multi-cluster Power Management)?

PSCI是arm定义的interface

MCPM是linaro introduce过来的

我的问题是

1. MCPM应该出现在PSCI之前,那么arm为什么不用linaro的方案而要再提出一个PSCI呢?

2. PSCI和MCPM之间有好坏之分么?

3. 对于以后LPM的趋势是什么呢?会主要都是用PSCI么?因为在ATF里面只有PSCI相关的sample code。多谢

2016-05-11 09:47

@zyq1228:多谢zyq1228分享MCPM的有关信息,我之前没有关注过。我的理解如下(不一定正确):

从名字看,MCPM是为了解决multi-cluster(即ARM的big·Little架构)的电源管理。因此,从立意上看,或者说从high-level的角度看,MCPM不是一个有大局观的东西,只是为了解决一个特定的问题。

而PSCI就不一样了,它的目标是提供一个通用的电源管理API,因此这个API里面可以包括当前的多核PM功能,也可以包括multi-cluster的东西,甚至可以包括后面的新花样。

所以从这个角度看,我觉得PSCI具备通用性、具备进化的能力,所以ARM把它当作了一个位于OS下面的一个相对固化的东西(可以称作firmware)。

而MCPM则无法获得同等地位,它只是linuxe kernel中的一个软件模块而已。

zyq1228

2016-05-11 19:18

@wowo:多谢wowo的分享,很有参考性!

zyq1228

2016-04-19 09:59

wowo,什么时候有PSCI的讲解啊?

2016-04-19 10:31

@zyq1228:抱歉,一直没有写PSCI的东西,等后面有时间,会结合ARM Trust Firmware讲。多谢关注~~

zyq1228

2016-04-19 11:21

@wowo:@wowo,好啊,ATF也我很感兴趣的东西,期待中

2016-04-19 14:23

@zyq1228:我不一定什么时候能写哦,要不你帮忙给大家写一下:-)

zyq1228

2016-04-25 13:24

@wowo:我还在学习中呢,希望看懂了之后可以写出和你一样好的东西

2016-04-25 21:23

@zyq1228:不碍事,大家都是边学边写~~

cym

2015-10-22 11:18

wowo 何时开讲psci呀?

2015-07-21 12:00

I'm back now

2015-07-21 12:19

@tigger:又忙了一阵子啊?

2015-07-21 13:12

@wowo:手头的事情少了一点点,抓紧时间补补课

发表评论:

昵称

邮件地址 (选填)

个人主页 (选填)

d4e3789769c8ad44c7e403863bfc3822.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值