cpu idle 驱动简介

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 框架

Linux cpuidle 驱动的作用,是在 CPU 空闲的时候进入硬件架构定义的某种省电状态(如 Intel ACPI 的 C-state,ARM 的 PSCI 规范定义的供电状态等),达到降低系统功耗的目的。Linux cpuidle 驱动框架大概如下(图片来自参考资料 [1]):

image

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]):

ea306557-5201-45f8-b53e-d61089833860

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分析

posted @ 2025-11-17 20:58  JiMoKuangXiangQu  阅读(62)  评论(0)    收藏  举报