cpu idle 驱动简介
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 框架
Linux cpuidle 驱动的作用,是在 CPU 空闲的时候进入硬件架构定义的某种省电状态(如 Intel ACPI 的 C-state,ARM 的 PSCI 规范定义的供电状态等),达到降低系统功耗的目的。Linux cpuidle 驱动框架大概如下(图片来自参考资料 [1]):

3. 细节
本文以 ARMv7 + Linux 4.14.x 进行分析。
3.1 cpuidle governor
cpuidle governor 类似于 cpufreq governor,决定了 CPU 进入 idle 状态的过程,Linux 4.14.x 有两个 cpuidle governor:
- ladder
- menu
正如它们的名字暗示的,ladder governor 在 CPU 进入 idle 时,会经历 idle 的各个阶段,不会跳变;而 menu governor 会直接进入选定的 idle 状态。两个 cpuidle governor 的工作工程一如下图所示(图片来自参考资料 [1]):

cpuidle governor 通过接口 cpuidle_register_governor() 注册,ladder governor 和 menu governor 注册过程分别如下:
static struct cpuidle_governor ladder_governor = {
.name = "ladder",
.rating = 10,
.enable = ladder_enable_device,
.select = ladder_select_state,
.reflect = ladder_reflect,
};
/**
* init_ladder - initializes the governor
*/
static int __init init_ladder(void)
{
/*
* When NO_HZ is disabled, or when booting with nohz=off, the ladder
* governor is better so give it a higher rating than the menu
* governor.
*/
if (!tick_nohz_enabled)
ladder_governor.rating = 25;
return cpuidle_register_governor(&ladder_governor);
}
postcore_initcall(init_ladder);
static struct cpuidle_governor menu_governor = {
.name = "menu",
.rating = 20,
.enable = menu_enable_device,
.select = menu_select,
.reflect = menu_reflect,
};
/**
* init_menu - initializes the governor
*/
static int __init init_menu(void)
{
return cpuidle_register_governor(&menu_governor);
}
postcore_initcall(init_menu);
int cpuidle_register_governor(struct cpuidle_governor *gov)
{
int ret = -EEXIST;
if (!gov || !gov->select)
return -EINVAL;
if (cpuidle_disabled())
return -ENODEV;
mutex_lock(&cpuidle_lock);
if (__cpuidle_find_governor(gov->name) == NULL) {
ret = 0;
list_add_tail(&gov->governor_list, &cpuidle_governors);
if (!cpuidle_curr_governor ||
cpuidle_curr_governor->rating < gov->rating)
cpuidle_switch_governor(gov); /* 优选当前 governor */
}
mutex_unlock(&cpuidle_lock);
return ret;
}
int cpuidle_switch_governor(struct cpuidle_governor *gov)
{
struct cpuidle_device *dev;
if (gov == cpuidle_curr_governor)
return 0;
cpuidle_uninstall_idle_handler();
if (cpuidle_curr_governor) {
list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
cpuidle_disable_device(dev);
}
cpuidle_curr_governor = gov;
if (gov) {
list_for_each_entry(dev, &cpuidle_detected_devices, device_list)
cpuidle_enable_device(dev);
cpuidle_install_idle_handler();
printk(KERN_INFO "cpuidle: using governor %s\n", gov->name);
}
return 0;
}
3.2 cpuidle driver
以 ARMv7 的 cpuidle 驱动为例:
static int __init arm_idle_init(void)
{
int cpu, ret;
struct cpuidle_driver *drv;
struct cpuidle_device *dev;
for_each_possible_cpu(cpu) {
drv = kmemdup(&arm_idle_driver, sizeof(*drv), GFP_KERNEL);
...
ret = dt_init_idle_driver(drv, arm_idle_state_match, 1); /* 解析 cpuidle DTS 配置 + 设置 idle 回调 */
...
ret = cpuidle_register_driver(drv); /* 注册 cpuidle 驱动 */
...
ret = arm_cpuidle_init(cpu); /* 从 PSCI 固件 获取 cpuidle_ops 接口 */
...
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
...
dev->cpu = cpu;
...
ret = cpuidle_register_device(dev); /* 注册 cpuidle 设备 */
...
}
}
一个 cpuidle 相关的 DTS 配置示例如下:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a15";
reg = <0>;
cci-control-port = <&cci_control1>;
cpu-idle-states = <&CLUSTER_SLEEP_BIG>;
capacity-dmips-mhz = <1024>;
};
cpu1: cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a15";
reg = <1>;
cci-control-port = <&cci_control1>;
cpu-idle-states = <&CLUSTER_SLEEP_BIG>;
capacity-dmips-mhz = <1024>;
};
cpu2: cpu@2 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0x100>;
cci-control-port = <&cci_control2>;
cpu-idle-states = <&CLUSTER_SLEEP_LITTLE>;
capacity-dmips-mhz = <516>;
};
cpu3: cpu@3 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0x101>;
cci-control-port = <&cci_control2>;
cpu-idle-states = <&CLUSTER_SLEEP_LITTLE>;
capacity-dmips-mhz = <516>;
};
cpu4: cpu@4 {
device_type = "cpu";
compatible = "arm,cortex-a7";
reg = <0x102>;
cci-control-port = <&cci_control2>;
cpu-idle-states = <&CLUSTER_SLEEP_LITTLE>;
capacity-dmips-mhz = <516>;
};
idle-states {
CLUSTER_SLEEP_BIG: cluster-sleep-big {
compatible = "arm,idle-state";
local-timer-stop;
entry-latency-us = <1000>;
exit-latency-us = <700>;
min-residency-us = <2000>;
};
CLUSTER_SLEEP_LITTLE: cluster-sleep-little {
compatible = "arm,idle-state";
local-timer-stop;
entry-latency-us = <1000>;
exit-latency-us = <500>;
min-residency-us = <2500>;
};
};
};
3.3 进入 cpuidle
当 CPU 上除 idle 进程外,不再有任何进程运行时,进入 CPU idle 状态:
void cpu_startup_entry(enum cpuhp_state state)
{
...
while (1)
do_idle();
}
static void do_idle(void)
{
...
while (!need_resched()) {
...
local_irq_disable();
...
if (cpu_idle_force_poll || tick_check_broadcast_expired())
cpu_idle_poll();
else
cpuidle_idle_call();
...
}
...
}
static void cpuidle_idle_call(void)
{
struct cpuidle_device *dev = cpuidle_get_device();
struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
int next_state, entered_state;
...
if (idle_should_enter_s2idle() || dev->use_deepest_state) {
if (idle_should_enter_s2idle()) {
entered_state = cpuidle_enter_s2idle(drv, dev); /* 进入 cpu s2idle 态 */
if (entered_state > 0) {
local_irq_enable();
goto exit_idle;
}
}
next_state = cpuidle_find_deepest_state(drv, dev);
call_cpuidle(drv, dev, next_state); /* 进入 cpu idle 态 */
} else {
/*
* Ask the cpuidle framework to choose a convenient idle state.
*/
next_state = cpuidle_select(drv, dev); /* 通过 governor 选择下一 cpu idle 状态 */
entered_state = call_cpuidle(drv, dev, next_state); /* 进入 cpu idle 态 */
/*
* Give the governor an opportunity to reflect on the outcome
*/
cpuidle_reflect(dev, entered_state); /* 告知 governor 进入的 cpu idle 状态 */
}
...
}
entered_state = cpuidle_enter_s2idle(drv, dev); /* 进入 cpu s2idle 态 */
enter_s2idle_proper()
drv->states[index].enter_s2idle(dev, drv, index);
arm_enter_idle_state()
next_state = cpuidle_select(drv, dev); /* 通过 governor 选择下一 cpu idle 状态 */
cpuidle_curr_governor->select(drv, dev)
menu_select()
call_cpuidle()
cpuidle_enter()
cpuidle_enter_state()
entered_state = target_state->enter(dev, drv, index);
arm_enter_idle_state()
cpuidle_reflect(dev, entered_state); /* 告知 governor 进入的 cpu idle 状态 */
cpuidle_curr_governor->reflect(dev, index)
menu_reflect()
arm_enter_idle_state() 最浅度省电状态实现为 dsb + wfi,更多的电源状态由 PSCI 固件实现。
4. 参考资料
[1] [https://blog.csdn.net/u010936265/article/details/147233825](Linux电源管理(三),CPUIdle 和 ARM的PSCI "https://blog.csdn.net/u010936265/article/details/147233825")
[2] linux cpu管理(五) cpu idle
[3] Linux cpu Idle机制
[4] 一文搞懂linux cpu idle
[5] cpu_ops、suspend_ops、arm_idle_driver以及machine_restart/machine_power_off到底层PSCI Firmware分析

浙公网安备 33010602011771号