Linux cpuidle framework(3)_ARM64 generic CPU idle driver

1. 前言

本文以ARM64平台下的cpuidle driver为例,说明怎样在cpuidle framework的框架下,编写cpuidle driver。另外,本文在描述cpuidle driver的同时,会涉及到CPU hotplug的概念,因此也可作为CPU hotplug的引子。

2. arm_idle_init

ARM generic CPU idle driver的代码位于“drivers/cpuidle/cpuidle-arm.c”中,它的入口函数是arm_idle_init,如下:

/*
 * arm_idle_init - Initializes arm cpuidle driver
 *
 * Initializes arm cpuidle driver for all CPUs, if any CPU fails
 * to register cpuidle driver then rollback to cancel all CPUs
 * registration.
 */
static int __init arm_idle_init(void)
{
    int cpu, ret;
    struct cpuidle_driver *drv;
    struct cpuidle_device *dev;

    // 遍历所有可能的 CPU
    for_each_possible_cpu(cpu) {
        // 调用 arm_idle_init_cpu 函数对每个 CPU 初始化 cpuidle 驱动
        ret = arm_idle_init_cpu(cpu);
        if (ret)
            goto out_fail;  // 如果初始化失败,则跳转到 out_fail 标签进行回滚操作
    }

    return 0;  // 初始化成功,返回 0

out_fail:
    // 回滚操作,取消已注册的 cpuidle 驱动
    while (--cpu >= 0) {
        // 获取当前 CPU 对应的 cpuidle 设备和驱动
        dev = per_cpu(cpuidle_devices, cpu);
        drv = cpuidle_get_cpu_driver(dev);

        // 注销 cpuidle 驱动并释放相关资源
        cpuidle_unregister(drv);
        kfree(drv);
    }

    return ret;  // 返回初始化失败的错误码
}

// 在设备初始化时调用 arm_idle_init 函数
device_initcall(arm_idle_init);
arm_idle_init 函数是 ARM 架构的 cpuidle 驱动的初始化函数。该函数负责对所有可能的 CPU 初始化 cpuidle 驱动,并在任何一个 CPU 初始化失败时执行回滚操作。

使用 for_each_possible_cpu 循环遍历系统中所有可能的 CPU。

对每个 CPU 调用 arm_idle_init_cpu 函数,该函数可能执行特定于 ARM 架构的 cpuidle 驱动的初始化。如果初始化失败(返回非零值),则跳转到标签 out_fail 进行回滚操作。

如果所有 CPU 成功初始化,函数返回 0 表示成功。

如果有任何一个 CPU 初始化失败,通过标签 out_fail 执行回滚操作。在回滚时,使用 per_cpu 宏获取每个 CPU 对应的 cpuidle 设备和驱动,然后使用 cpuidle_unregister 注销 cpuidle 驱动,并使用 kfree 释放相关资源。

device_initcall(arm_idle_init) 将 arm_idle_init 函数注册为设备初始化函数,确保在系统启动时调用该函数进行 cpuidle 驱动的初始化。

总体而言,这段代码是 ARM 架构中用于初始化 cpuidle 驱动的代码,它通过遍历所有可能的 CPU 来初始化每个 CPU 上的 cpuidle 驱动,同时处理初始化失败的回滚操作

/*
 * arm_idle_init_cpu
 *
 * Registers the arm specific cpuidle driver with the cpuidle
 * framework. It relies on core code to parse the idle states
 * and initialize them using driver data structures accordingly.
 */
static int __init arm_idle_init_cpu(int cpu)
{
    int ret;
    struct cpuidle_driver *drv;

    // 通过 kmemdup 复制 arm_idle_driver 结构体,分配内存并初始化 cpuidle 驱动
    drv = kmemdup(&arm_idle_driver, sizeof(*drv), GFP_KERNEL);
    if (!drv)
        return -ENOMEM;  // 内存分配失败,返回错误码 -ENOMEM

    // 设置 cpumask,指定对应 CPU
    drv->cpumask = (struct cpumask *)cpumask_of(cpu);

    /*
     * Initialize idle states data, starting at index 1.  This
     * driver is DT only, if no DT idle states are detected (ret
     * == 0) let the driver initialization fail accordingly since
     * there is no reason to initialize the idle driver if only
     * wfi is supported.
     */
    // 使用 dt_init_idle_driver 初始化驱动的空闲状态数据,从索引 1 开始
    ret = dt_init_idle_driver(drv, arm_idle_state_match, 1);
    if (ret <= 0) {
        ret = ret ? : -ENODEV;  // 如果没有检测到 DT 空闲状态,设置错误码为 -ENODEV
        goto out_kfree_drv;  // 跳转到标签 out_kfree_drv 进行错误处理
    }

    /*
     * Call arch CPU operations in order to initialize
     * idle states suspend back-end specific data
     */
    // 调用 arm_cpuidle_init 初始化 CPU 的空闲状态挂起后端的特定数据
    ret = arm_cpuidle_init(cpu);

    /*
     * Allow the initialization to continue for other CPUs, if the
     * reported failure is a HW misconfiguration/breakage (-ENXIO).
     *
     * Some platforms do not support idle operations
     * (arm_cpuidle_init() returning -EOPNOTSUPP), we should
     * not flag this case as an error, it is a valid
     * configuration.
     */
    // 如果初始化失败且错误码不是 -EOPNOTSUPP,则打印错误信息,但继续初始化其他 CPU
    if (ret) {
        if (ret != -EOPNOTSUPP)
            pr_err("CPU %d failed to init idle CPU ops\n", cpu);
        ret = ret == -ENXIO ? 0 : ret;  // 如果错误码是 -ENXIO,则设置为 0,表示继续初始化其他 CPU
        goto out_kfree_drv;  // 跳转到标签 out_kfree_drv 进行错误处理
    }

    // 注册 cpuidle 驱动
    ret = cpuidle_register(drv, NULL);
    if (ret)
        goto out_kfree_drv;  // 注册失败,跳转到标签 out_kfree_drv 进行错误处理

    // 注册 cpuidle 驱动的冷却设备
    cpuidle_cooling_register(drv);

    return 0;  // 初始化成功,返回 0

out_kfree_drv:
    // 错误处理,释放已分配的内存
    kfree(drv);
    return ret;  // 返回错误码
}
arm_idle_init_cpu 函数用于初始化 ARM 架构特定的 cpuidle 驱动,注册到 cpuidle 框架中。

使用 kmemdup 复制 arm_idle_driver 结构体,该结构体包含 ARM 架构的 cpuidle 驱动信息。

设置 cpumask,指定对应 CPU。

使用 dt_init_idle_driver 初始化驱动的空闲状态数据,从索引 1 开始。如果没有检测到 DT 空闲状态,设置错误码为 -ENODEV。

调用 arm_cpuidle_init 初始化 CPU 的空闲状态挂起后端的特定数据。

允许初始化继续进行其他 CPU,如果报告的失败是硬件配置错误(-ENXIO)。

如果初始化失败且错误码不是 -EOPNOTSUPP,则打印错误信息,但继续初始化其他 CPU。如果错误码是 -ENXIO,则设置为 0,表示继续初始化其他 CPU。

注册 cpuidle 驱动,如果注册失败,则跳转到标签 out_kfree_drv 进行错误处理。

注册 cpuidle 驱动的冷却设备。

如果所有步骤成功,则返回 0 表示初始化成功。如果发生错误,通过标签 out_kfree_drv 进行错误处理,释放已分配的内存并返回相应的错误码。
static struct cpuidle_driver arm_idle_driver __initdata = {
	.name = "arm_idle",
	.owner = THIS_MODULE,
	/*
	 * State at index 0 is standby wfi and considered standard
	 * on all ARM platforms. If in some platforms simple wfi
	 * can't be used as "state 0", DT bindings must be implemented
	 * to work around this issue and allow installing a special
	 * handler for idle state index 0.
	 */
	.states[0] = {
		.enter                  = arm_enter_idle_state,
		.exit_latency           = 1,
		.target_residency       = 1,
		.power_usage		= UINT_MAX,
		.name                   = "WFI",
		.desc                   = "ARM WFI",
	}
};
arm_idle_driver 是一个静态的 struct cpuidle_driver 结构体,用于定义 ARM 架构的 cpuidle 驱动。

__initdata 标识符表示该结构体是用于初始化过程的数据,且在初始化完成后可以被丢弃。

name 字段设置了 cpuidle 驱动的名称为 "arm_idle"。

owner 字段设置了 cpuidle 驱动的所有者为当前模块(THIS_MODULE 是指当前编写的内核模块)。

states[0] 定义了 cpuidle 的第一个状态(索引为 0),通常代表的是 "standby wfi" 状态。这个状态在所有 ARM 平台上被认为是标准状态。注释中提到,如果在某些平台上简单的 wfi 不能用作 "state 0",则需要实现 DT 绑定来解决此问题,并允许为空闲状态索引 0 安装特殊处理程序。

对于 "state 0",定义了以下参数:
    enter: 进入空闲状态时调用的函数,此处为 arm_enter_idle_state。
    exit_latency: 退出空闲状态的延迟,设置为 1。
    target_residency: 目标停留时间,设置为 1。
    power_usage: 预计功耗,设置为 UINT_MAX(表示无限大)。
    name: 状态的名称,设置为 "WFI"。
    desc: 状态的描述,设置为 "ARM WFI"。

这个结构体定义了 ARM 架构的 cpuidle 驱动的基本特性和参数。

关于 sysfs 节点,这些变量和结构体主要体现在 /sys/devices/system/cpu/cpuidle/ 目录下。具体来说,以下节点是相关的:

/sys/devices/system/cpu/cpuidle/driver[arm_idle]/:包含有关 arm_idle 驱动的信息,例如驱动的名称、所有者等。

/sys/devices/system/cpu/cpuidle/current_driver:指示当前正在使用的 cpuidle 驱动的名称。

/sys/devices/system/cpu/cpuidle/current_governor:指示当前正在使用的 cpuidle 调度器(governor)的名称。

/sys/devices/system/cpu/cpuidle/ 下的其他子目录和文件可能包含有关空闲状态的信息,例如功耗、延迟等。具体的节点结构和名称可能因实际系统和内核配置而异。

3. dt_init_idle_driver

dt_init_idle_driver函数用于从DTS中解析出cpuidle states的信息,并初始化arm64_idle_driver中的states数组。在分析这个函数之前,我们先来看一下cpuidle相关的DTS源文件是怎么写的:

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		idle-states {
			entry-method = "psci";

			cpu_pd_wait: cpu-pd-wait {
				compatible = "arm,idle-state";
				arm,psci-suspend-param = <0x0010033>;
				local-timer-stop;
				entry-latency-us = <1000>;
				exit-latency-us = <700>;
				min-residency-us = <2700>;
				wakeup-latency-us = <1500>;
			};
		};

		A53_0: cpu@0 {
			device_type = "cpu";
			compatible = "arm,cortex-a53";
			reg = <0x0>;
			clock-latency = <61036>;
			clocks = <&clk IMX8MP_CLK_ARM>;
			enable-method = "psci";
			i-cache-size = <0x8000>;
			i-cache-line-size = <64>;
			i-cache-sets = <256>;
			d-cache-size = <0x8000>;
			d-cache-line-size = <64>;
			d-cache-sets = <128>;
			next-level-cache = <&A53_L2>;
			nvmem-cells = <&cpu_speed_grade>;
			nvmem-cell-names = "speed_grade";
			operating-points-v2 = <&a53_opp_table>;
			#cooling-cells = <2>;
			cpu-idle-states = <&cpu_pd_wait>;
		};

		A53_1: cpu@1 {
			device_type = "cpu";
			compatible = "arm,cortex-a53";
			reg = <0x1>;
			clock-latency = <61036>;
			clocks = <&clk IMX8MP_CLK_ARM>;
			enable-method = "psci";
			i-cache-size = <0x8000>;
			i-cache-line-size = <64>;
			i-cache-sets = <256>;
			d-cache-size = <0x8000>;
			d-cache-line-size = <64>;
			d-cache-sets = <128>;
			next-level-cache = <&A53_L2>;
			operating-points-v2 = <&a53_opp_table>;
			#cooling-cells = <2>;
			cpu-idle-states = <&cpu_pd_wait>;
		};

		A53_2: cpu@2 {
			device_type = "cpu";
			compatible = "arm,cortex-a53";
			reg = <0x2>;
			clock-latency = <61036>;
			clocks = <&clk IMX8MP_CLK_ARM>;
			enable-method = "psci";
			i-cache-size = <0x8000>;
			i-cache-line-size = <64>;
			i-cache-sets = <256>;
			d-cache-size = <0x8000>;
			d-cache-line-size = <64>;
			d-cache-sets = <128>;
			next-level-cache = <&A53_L2>;
			operating-points-v2 = <&a53_opp_table>;
			#cooling-cells = <2>;
			cpu-idle-states = <&cpu_pd_wait>;
		};

		A53_3: cpu@3 {
			device_type = "cpu";
			compatible = "arm,cortex-a53";
			reg = <0x3>;
			clock-latency = <61036>;
			clocks = <&clk IMX8MP_CLK_ARM>;
			enable-method = "psci";
			i-cache-size = <0x8000>;
			i-cache-line-size = <64>;
			i-cache-sets = <256>;
			d-cache-size = <0x8000>;
			d-cache-line-size = <64>;
			d-cache-sets = <128>;
			next-level-cache = <&A53_L2>;
			operating-points-v2 = <&a53_opp_table>;
			#cooling-cells = <2>;
			cpu-idle-states = <&cpu_pd_wait>;
		};

		A53_L2: l2-cache0 {
			compatible = "cache";
			cache-level = <2>;
			cache-size = <0x80000>;
			cache-line-size = <64>;
			cache-sets = <512>;
		};
	};
idle-states:这是一个 Device Tree 中的节点,用于描述 CPU 空闲状态的配置信息。

entry-method = "psci";:指定了空闲状态的进入方法为 PSCI(Power State Coordination Interface)。

cpu_pd_wait:这是具体的 CPU 空闲状态的描述符,可以根据实际需要定义多个不同的空闲状态。

compatible = "arm,idle-state";:指定了这个节点兼容的设备或驱动类型,表示这是一个 ARM 架构的空闲状态。

arm,psci-suspend-param = <0x0010033>;:指定了 PSCI 中的挂起参数,用于在进入空闲状态时传递给 PSCI 接口。在这里,参数值为 0x0010033。

local-timer-stop;:指定在进入空闲状态时停止本地定时器。

entry-latency-us = <1000>;:定义了进入空闲状态的延迟,单位是微秒,这里设置为 1000 微秒。

exit-latency-us = <700>;:定义了退出空闲状态的延迟,单位是微秒,这里设置为 700 微秒。

min-residency-us = <2700>;:定义了最小停留时间,单位是微秒,这里设置为 2700 微秒。

wakeup-latency-us = <1500>;:定义了唤醒空闲状态的延迟,单位是微秒,这里设置为 1500 微秒。

总体而言,这段 DTS 描述了一个名为 cpu-pd-wait 的 CPU 空闲状态,采用 PSCI 进行挂起,停止本地定时器,具有特定的延迟和参数设置。这样的信息对于系统调度和功耗管理非常重要,操作系统可以根据这些信息有效地进行空闲状态的管理和调度.

结合上面的DTS信息,dt_init_idle_driver函数的执行过程如下。

/**
 * dt_init_idle_driver() - Parse the DT idle states and initialize the
 *			   idle driver states array
 * @drv:	  Pointer to CPU idle driver to be initialized
 * @matches:	  Array of of_device_id match structures to search in for
 *		  compatible idle state nodes. The data pointer for each valid
 *		  struct of_device_id entry in the matches array must point to
 *		  a function with the following signature, that corresponds to
 *		  the CPUidle state enter function signature:
 *
 *		  int (*)(struct cpuidle_device *dev,
 *			  struct cpuidle_driver *drv,
 *			  int index);
 *
 * @start_idx:    First idle state index to be initialized
 *
 * If DT idle states are detected and are valid the state count and states
 * array entries in the cpuidle driver are initialized accordingly starting
 * from index start_idx.
 *
 * Return: number of valid DT idle states parsed, <0 on failure
 */
int dt_init_idle_driver(struct cpuidle_driver *drv,
			const struct of_device_id *matches,
			unsigned int start_idx)
{
	struct cpuidle_state *idle_state;
	struct device_node *state_node, *cpu_node;
	const struct of_device_id *match_id;
	int i, err = 0;
	const cpumask_t *cpumask;
	unsigned int state_idx = start_idx;

	if (state_idx >= CPUIDLE_STATE_MAX)
		return -EINVAL;  // 如果开始的索引大于等于 CPUIDLE_STATE_MAX,返回无效参数错误

	/*
	 * We get the idle states for the first logical cpu in the
	 * driver mask (or cpu_possible_mask if the driver cpumask is not set)
	 * and we check through idle_state_valid() if they are uniform
	 * across CPUs, otherwise we hit a firmware misconfiguration.
	 */
	// 获取驱动掩码(或者如果驱动掩码没有设置,则使用 cpu_possible_mask)的第一个逻辑 CPU 的空闲状态
	cpumask = drv->cpumask ? : cpu_possible_mask;
	cpu_node = of_cpu_device_node_get(cpumask_first(cpumask));

	for (i = 0; ; i++) {
		// 获取 CPU 状态节点
		state_node = of_get_cpu_state_node(cpu_node, i);
		if (!state_node)
			break;  // 如果没有更多的状态节点,退出循环

		// 在 matches 中查找匹配的设备节点
		match_id = of_match_node(matches, state_node);
		if (!match_id) {
			err = -ENODEV;  // 如果没有匹配的设备节点,返回设备不存在错误
			break;
		}

		// 检查设备节点是否可用
		if (!of_device_is_available(state_node)) {
			of_node_put(state_node);
			continue;  // 如果设备节点不可用,继续下一个循环
		}

		// 检查空闲状态是否有效
		if (!idle_state_valid(state_node, i, cpumask)) {
			pr_warn("%pOF idle state not valid, bailing out\n",
				state_node);
			err = -EINVAL;  // 如果空闲状态无效,返回无效参数错误
			break;
		}

		if (state_idx == CPUIDLE_STATE_MAX) {
			pr_warn("State index reached static CPU idle driver states array size\n");
			break;  // 如果状态索引达到静态 CPU 空闲驱动状态数组的大小,退出循环
		}

		// 初始化空闲状态节点
		idle_state = &drv->states[state_idx++];
		err = init_state_node(idle_state, match_id, state_node);
		if (err) {
			pr_err("Parsing idle state node %pOF failed with err %d\n",
			       state_node, err);
			err = -EINVAL;  // 如果初始化失败,返回无效参数错误
			break;
		}
		of_node_put(state_node);
	}

	of_node_put(state_node);
	of_node_put(cpu_node);

	if (err)
		return err;  // 如果发生错误,返回错误码

	/* Set the number of total supported idle states. */
	// 设置总支持的空闲状态数量
	drv->state_count = state_idx;

	/*
	 * Return the number of present and valid DT idle states, which can
	 * also be 0 on platforms with missing DT idle states or legacy DT
	 * configuration predating the DT idle states bindings.
	 */
	// 返回存在且有效的 DT 空闲状态的数量,也可能是 0,表示缺少 DT 空闲状态或者遗留的 DT 配置先于 DT 空闲状态绑定
	return state_idx - start_idx;
}
EXPORT_SYMBOL_GPL(dt_init_idle_driver);

dt_init_idle_driver 函数用于解析设备树(Device Tree)中的空闲状态并初始化 cpuidle 驱动的状态数组。

参数:

drv:指向要初始化的 CPU 空闲驱动的指针。
matches:of_device_id 结构体数组,用于匹配设备节点。
start_idx:空闲状态数组的起始索引。

函数首先检查起始索引是否超过了数组的最大索引值,如果是,则返回无效参数错误。

通过获取驱动的掩码(或者使用 cpu_possible_mask 如果掩码未设置),获得第一个逻辑 CPU 的设备节点。

在循环中,获取每个 CPU 状态节点,然后检查匹配和有效性。如果没有更多的状态节点,则退出循环。

初始化空闲状态节点,包括从设备节点中解析状态信息,并设置到 cpuidle 驱动的状态数组中。

如果发生错误,返回相应的错误码。否则,设置 cpuidle 驱动支持的总空闲状态数量。

最后返回存在且有效的 DT 空闲状态的数量,可能为 0。

这个函数主要用于从设备树中读取空闲状态信息,因此相关的 sysfs 节点和文件通常是通过设备树提供的。

4.arm_enter_idle_state

/*
 * arm_enter_idle_state - Programs CPU to enter the specified state
 *
 * dev: cpuidle device
 * drv: cpuidle driver
 * idx: state index
 *
 * Called from the CPUidle framework to program the device to the
 * specified target state selected by the governor.
 */
static __cpuidle int arm_enter_idle_state(struct cpuidle_device *dev,
					  struct cpuidle_driver *drv, int idx)
{
	/*
	 * Pass idle state index to arm_cpuidle_suspend which in turn
	 * will call the CPU ops suspend protocol with idle index as a
	 * parameter.
	 */
	return CPU_PM_CPU_IDLE_ENTER(arm_cpuidle_suspend, idx);
}

arm_enter_idle_state 函数是 ARM 架构中用于进入指定空闲状态的函数。它是由 CPUidle 框架调用的,目的是将设备设置到由 governor 选择的特定目标状态。

参数:
    dev:指向 cpuidle 设备的指针。
    drv:指向 cpuidle 驱动的指针。
    idx:空闲状态的索引。

函数内部调用了 CPU_PM_CPU_IDLE_ENTER 宏,将空闲状态的索引传递给 arm_cpuidle_suspend 函数。该宏用于执行与 CPU 操作挂起协议相关的操作,其中传递了空闲状态的索引作为参数。

返回值是函数调用的结果,通常表示进入空闲状态的成功与否。这个返回值将被 CPUidle 框架用于进一步的处理,以通知系统是否成功进入了指定的空闲状态。

总体而言,这个函数负责将设备设置到指定的空闲状态,它通过调用底层的 arm_cpuidle_suspend 函数来实现挂起协议的执行,以便进入空闲状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值