参考:Linux cpuidle framework(1)_概述和软件架构
前言
在Linux系统中,CPU被两类程序占用,一类是进程(或线程),也称进程上下文;一类是各种中断、异常的处理程序,也称中断上下文。当CPU上没有进程需要运行,也没有异常发生,则cpu就处于无所事事的状态,也就是idle状态,Linux kernel抽象了cpuidle framework框架用来管理这种状态。
CPUIDLE Framework
linux kernel中,cpuidle framework位于drivers/cpuidle目录中,包含cpuidle core, cpuidle governors和cpuidle driver三个模块,在结合位于kernel/sched目录中的cpuidle entry,共同完成cpu的idle管理,软件架构如下图:
kernel sched模块:位于kernel/sched/idle.c中,负责实现idle线程的通用入口(cpuidle entry)逻辑,包括idle模式的选择,idle的进入等
cpuidle core:位于drivers/cpuidle,负责cpuidle framework的整体框架,主要功能包括:根据cpuidle的应用场景抽象出cpuidle device、cpuidle driver、cpuidle governor三个实体;以函数调用的形式,向上层sched模块提供接口;以sysfs的形式,向用户空间提供接口;向下层的cpuidle drivers模块,提供统一的driver注册和管理接口;向下层的governors模块,提供统一的governor注册和管理接口。相关文件cpuidle.c driver.c governor.c sysfs.c
cpuidle drivers:负责idle机制的实现,即如何进入idle状态,什么条件下退出等
cpuidle governors:位于drivers/cpuidle/governors目录,负责提供多种idle level,governor选择一种方案执行
重要数据结构
struct cpuidle_state
很多复杂的cpu,有多种不同的idle级别,这些idle级别有不同的功耗和延迟,从而可以在不同的场景下使用,linux kernel使用struct cpuidle_state结构抽象idle level。
参考:Linux kernel5.4 drivers/cpuidle/cpuidle-psci.c
在结构体cpuidle_driver中初始化cpuidle_state结构体如下:
static struct cpuidle_driver psci_idle_driver __initdata = {
.name = "psci_idle",
.owner = THIS_MODULE,
/*
* PSCI idle states relies on architectural WFI to
* be represented as state index 0.
*/
.states[0] = {
.enter = psci_enter_idle_state,
.exit_latency = 1,
.target_residency = 1,
.power_usage = UINT_MAX,
.name = "WFI",
.desc = "ARM WFI",
}
};
结构体struct cpuidle_state变量state[]
.enter:进入该idle状态的回调函数。
.target-residency:进入该idle状态最少停留时间
.power_usage:在该idle状态下的功耗
.exit_latency:退出该idle状态的延迟
CPUIDLE driver和device的注册
代码:drivers/cpuidle/cpuidle-psci.c
CPUIDLE DTS 配置
以某arm64平台DTS配置为例,idle-states节点表示支持的cpu idle的具体信息,每个cpu通过cpu-idle-states属性表示可以支持的cpu idle,根据cpu idle的程度可以支持多个,该示例中只支持一个,cpu-idle-states属性支持的cpu idle信息被添加到cpuidle driver的states数组的1号成员或之后的成员中,该数组的0号成员是cpu通过执行wfi指令而达到的禁止状态,states数组成员必须按照功耗由大到小进行排序。
cpus {
#address-cells = <2>;
#size-cells = <0>;
idle-states {
entry-method = "arm,psci";
CPU_OFF_LIT: pm-cpu-level0 {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010012>;
local-timer-stop;
entry-latency-us = <100>;
exit-latency-us = <100>;
min-residency-us = <5000>;
};
CPU_OFF_BIG: pm-cpu-level1 {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010012>;
local-timer-stop;
entry-latency-us = <100>;
exit-latency-us = <100>;
min-residency-us = <5000>;
};
};
a55_cpu0: cpu@400 {
device_type = "cpu";
compatible = "arm,cortex-a55", "arm,armv8";
reg = <0x0 0x400>;
enable-method = "psci";
cpu-idle-states = <&CPU_OFF_BIG>;
};
};
CPU0的state[1]就对应CPU_OFF_BIG状态,该状态对应的enter函数为psci_enter_idle_state
static const struct of_device_id
psci_idle_state_match[] __initconst = {
{ .compatible = "arm,idle-state",
.data = psci_enter_idle_state },
{ },
};
CPUIDLE enter流程
Boot cpu启动完成后会把init task变为idle thread,然后调用cpu_startup_entry函数让cpu进入到idle
其他cpu在启动之前会为每个cpu创建idle thread,启动完成后调用cpu_startup_entry函数让cpu进入到idle
cpu_startup_entry流程:
cpuidle_select,通过cpuidle governor,选择一个cpuidle state
target_state->enter(), 进入目标状态的处理函数,在该示例中cpuidle_state结构体初始化完成后,支持两个level的cpuidle state,两个cpuidle_state的进入函数均为psci_enter_idle_state。psci_enter_idle_state流程如下图:
对于states[0], 通过cpu_do_idle函数最终调用wfi执行让cpu进入idle
对于states[1],通过invoke_psci_fn()函数,陷入到bl31,让cpu进入idle。Invoke_psci_fn回调函数在set_conduit()函数中根据dts配置的smc方法,初始化为__invoke_psci_fn_smc()函数;invoke_psci_fn_smc()函数最终调用smc指令,让系统陷入到bl31执行。
psci {
compatible = "arm,psci-1.0";
method = "smc";
};
arch/arm64/kernel/smccc-call.S
ENTRY(__arm_smccc_smc)
SMCCC smc
ENDPROC(__arm_smccc_smc)
EXPORT_SYMBOL(__arm_smccc_smc)
SMC陷入bl31流程
1. 设置vbar_el3异常向量表入口为runtime_exceptions
2. 异常发生后跳转到异常向量表runtime_exceptions,在runtime_exceptions中根据同步异常类型跳到PC(x15)指针处执行std_svc_smc_handle()函数处理异常
3. std_svc_smc_handle()函数处理异常,通过kernel传入的fn即0xC4000001进入到PSCI_CPU_SUSPEND_AARCH64分支执行cpu idle操作psci_plat_pm_ops在各个平台的驱动中初始化