如何实现一个新的 CPUFreq 处理器驱动程序

作者

1. 应该做什么?

你刚刚获得了一个全新的 CPU / 芯片组,并且有数据手册,想要为这个 CPU / 芯片组添加 cpufreq 支持吗?太棒了。这里有一些关于需要做什么的提示

1.1 初始化

首先,在 __initcall 级别 7 (module_init()) 或更高版本的函数中,检查此内核是否在正确的 CPU 和正确的芯片组上运行。如果是,则使用 cpufreq_register_driver() 向 CPUfreq 核心注册一个 struct cpufreq_driver

这个 struct cpufreq_driver 应该包含什么?

.name - 此驱动程序的名称。

.init - 指向每个策略初始化函数的指针。

.verify - 指向“验证”函数的指针。

.setpolicy _or_ .fast_switch _or_ .target _or_ .target_index - 请参阅下面的差异。

以及可选的

.flags - CPUfreq 核心的提示。

.driver_data - cpufreq 驱动程序特定数据。

.get_intermediate 和 target_intermediate - 用于在更改 CPU 频率时切换到稳定频率。

.get - 返回 CPU 的当前频率。

.bios_limit - 返回 CPU 的 HW/BIOS 最大频率限制。

.exit - 指向每个策略清理函数的指针,该函数在 CPU 热插拔过程的 CPU_POST_DEAD 阶段调用。

.suspend - 指向每个策略挂起函数的指针,该函数在中断禁用且_在_ governor 停止用于策略之后调用。

.resume - 指向每个策略恢复函数的指针,该函数在中断禁用且_在_ governor 再次启动之前调用。

.ready - 指向每个策略就绪函数的指针,该函数在策略完全初始化后调用。

.attr - 指向 NULL 终止的 “struct freq_attr” 列表的指针,该列表允许将值导出到 sysfs。

.boost_enabled - 如果设置,则启用加速频率。

.set_boost - 指向每个策略函数的指针,用于启用/禁用加速频率。

1.2 每个 CPU 初始化

每当向设备模型注册新的 CPU 时,或者在 cpufreq 驱动程序注册自身之后,如果没有 CPUfreq 策略存在于 CPU 上,则会调用每个策略初始化函数 cpufreq_driver.init。请注意,.init() 和 .exit() 例程仅对策略调用一次,而不是对策略管理的每个 CPU 调用。它接受一个 struct cpufreq_policy *policy 作为参数。现在该怎么办?

如有必要,激活 CPU 上对 CPUfreq 的支持。

然后,驱动程序必须填写以下值

policy->cpuinfo.min_freq _and_ policy->cpuinfo.max_freq

此 CPU 支持的最小和最大频率(以 kHz 为单位)

policy->cpuinfo.transition_latency

在此 CPU 上在两个频率之间切换所需的时间,以纳秒为单位(如果适用,否则指定 CPUFREQ_ETERNAL)

policy->cur

此 CPU 的当前工作频率(如果适用)

policy->min、policy->max、policy->policy 以及(如果必要)policy->governor

必须包含此 CPU 的“默认策略”。稍后,将使用这些值调用 cpufreq_driver.verify 和 cpufreq_driver.setpolicy 或 cpufreq_driver.target/target_index。

policy->cpus

使用与此 CPU 一起进行 DVFS 的(在线 + 离线)CPU 的掩码更新此项(即,与它共享时钟/电压轨)。

对于设置其中一些值(cpuinfo.min[max]_freq、policy->min[max])而言,频率表助手可能会有所帮助。有关它们的更多信息,请参见第 2 节。

1.3 验证

当用户决定设置新策略(包括“策略,governor,min,max”)时,必须验证此策略,以便可以纠正不兼容的值。对于验证这些值,cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min_freq, unsigned int max_freq) 函数可能会有所帮助。有关频率表助手的详细信息,请参见第 2 节。

您需要确保至少一个有效频率(或工作范围)在 policy->min 和 policy->max 之内。如有必要,首先增加 policy->max,并且仅在没有解决方案时,才减少 policy->min。

1.4 target 或 target_index 或 setpolicy 或 fast_switch?

大多数 cpufreq 驱动程序,甚至大多数 CPU 频率调节算法,都只允许将 CPU 频率设置为预定义的固定值。对于这些,您可以使用 ->target()、->target_index() 或 ->fast_switch() 回调。

一些支持 cpufreq 的处理器可以自行在一定限制之间切换频率。这些应使用 ->setpolicy() 回调。

1.5. target/target_index

target_index 调用有两个参数:struct cpufreq_policy *policyunsigned int 索引(指向公开的频率表)。

CPUfreq 驱动程序必须在此处调用时设置新频率。实际频率必须由 freq_table[index].frequency 确定。

在发生错误时,即使我们之前切换到中间频率,它也应始终恢复到早期频率(即 policy->restore_freq)。

已弃用

target 调用有三个参数:struct cpufreq_policy *policy、unsigned int target_frequency、unsigned int relation。

CPUfreq 驱动程序必须在此处调用时设置新频率。实际频率必须使用以下规则确定

  • 保持接近 “target_freq”

  • policy->min <= new_freq <= policy->max (这必须有效!!!)

  • 如果 relation==CPUFREQ_REL_L,请尝试选择大于或等于 target_freq 的 new_freq。(“L 代表最低,但不低于”)

  • 如果 relation==CPUFREQ_REL_H,请尝试选择小于或等于 target_freq 的 new_freq。(“H 代表最高,但不高于”)

同样,频率表助手可以为您提供帮助 - 有关详细信息,请参见第 2 节。

1.6. fast_switch

此函数用于从调度程序的上下文中进行频率切换。并非所有驱动程序都需要实现它,因为不允许从此回调中休眠。此回调必须经过高度优化,才能尽可能快地进行切换。

此函数有两个参数:struct cpufreq_policy *policyunsigned int target_frequency

1.7 setpolicy

setpolicy 调用仅接受一个 struct cpufreq_policy *policy 作为参数。您需要将处理器内或芯片组内动态频率切换的下限设置为 policy->min,上限设置为 policy->max,并且 - 如果支持 - 当 policy->policy 为 CPUFREQ_POLICY_PERFORMANCE 时,选择以性能为导向的设置,而当 CPUFREQ_POLICY_POWERSAVE 时,选择以省电为导向的设置。另请查看 drivers/cpufreq/longrun.c 中的参考实现

1.8 get_intermediate 和 target_intermediate

仅适用于具有 target_index() 且 CPUFREQ_ASYNC_NOTIFICATION 未设置的驱动程序。

get_intermediate 应该返回平台想要切换到的稳定中间频率,而 target_intermediate() 应该在跳转到与 ‘index’ 对应的频率之前,将 CPU 设置为该频率。核心将负责发送通知,并且驱动程序不必在 target_intermediate() 或 target_index() 中处理它们。

如果驱动程序不想为某些目标频率切换到中间频率,则可以从 get_intermediate() 返回 ‘0’。在这种情况下,核心将直接调用 ->target_index()。

注意:在发生故障时,->target_index() 应该恢复到 policy->restore_freq,因为核心会发送该通知。

2. 频率表助手

由于大多数 cpufreq 处理器只允许设置为一些特定频率,因此带有某些函数的“频率表”可能有助于处理器驱动程序的一些工作。这样的“频率表”由 struct cpufreq_frequency_table 条目的数组组成,其中 “driver_data” 中包含驱动程序特定的值,“frequency” 中包含对应的频率,并且设置了标志。在表的末尾,您需要添加一个频率设置为 CPUFREQ_TABLE_END 的 cpufreq_frequency_table 条目。如果您想跳过表中的一个条目,请将频率设置为 CPUFREQ_ENTRY_INVALID。条目不需要按任何特定顺序排序,但如果它们已排序,则 cpufreq 核心会更快地为它们执行 DVFS,因为搜索最佳匹配的速度更快。

如果策略的 policy->freq_table 字段中包含有效的指针,则 cpufreq 表由核心自动验证。

cpufreq_frequency_table_verify() 确保至少一个有效频率在 policy->min 和 policy->max 之内,并且满足所有其他条件。这对于 ->verify 调用很有帮助。

cpufreq_frequency_table_target() 是 ->target 阶段的对应频率表助手。只需将值传递给此函数,此函数将返回包含 CPU 应设置到的频率的频率表条目。

以下宏可用作 cpufreq_frequency_table 上的迭代器

cpufreq_for_each_entry(pos, table) - 迭代频率表的所有条目。

cpufreq_for_each_valid_entry(pos, table) - 迭代所有条目,不包括 CPUFREQ_ENTRY_INVALID 频率。使用参数 “pos” - 一个 cpufreq_frequency_table * 作为循环光标,而 “table” - 你想要迭代的 cpufreq_frequency_table *

例如

struct cpufreq_frequency_table *pos, *driver_freq_table;

cpufreq_for_each_entry(pos, driver_freq_table) {
        /* Do something with pos */
        pos->frequency = ...
}

如果您需要使用 driver_freq_table 中 pos 的位置,请不要减去指针,因为它非常昂贵。相反,请使用宏 cpufreq_for_each_entry_idx() 和 cpufreq_for_each_valid_entry_idx()。