如何实现新的 CPUFreq 处理器驱动程序¶
作者
Dominik Brodowski <linux@brodo.de>
Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Viresh Kumar <viresh.kumar@linaro.org>
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 - 指向每个策略挂起函数的指针,该函数在禁用中断且_在_策略的调速器停止之后调用。
.resume - 指向每个策略恢复函数的指针,该函数在禁用中断且_在_调速器再次启动之前调用。
.ready - 指向在策略完全初始化后调用的每个策略准备函数的指针。
.attr - 指向以 NULL 结尾的“struct freq_attr”列表的指针,该列表允许将值导出到 sysfs。
.boost_enabled - 如果设置,则启用加速频率。
.set_boost - 指向每个策略函数的指针,用于启用/禁用加速频率。
1.2 每个 CPU 的初始化¶
每当向设备模型注册新的 CPU 时,或者在 cpufreq 驱动程序自行注册之后,如果 CPU 不存在 cpufreq 策略,则会调用每个策略初始化函数 cpufreq_driver.init。请注意,.init() 和 .exit() 例程仅针对策略调用一次,而不是针对策略管理的每个 CPU 调用。它以 struct cpufreq_policy *policy
作为参数。现在做什么?
如果必要,请在您的 CPU 上激活 CPUfreq 支持。
然后,驱动程序必须填写以下值
policy->cpuinfo.min_freq _和_ 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 验证¶
当用户决定设置新策略(由“policy,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 *policy
和 unsigned int
index(进入暴露的频率表)。
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 *policy
和 unsigned 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() 应将 CPU 设置为该频率,然后再跳转到与“index”对应的频率。核心将负责发送通知,驱动程序不必在 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()。