设备的能量模型
1. 概述
能量模型(EM)框架充当了一个接口,连接了了解各种性能水平下设备功耗的驱动程序和愿意利用该信息做出节能决策的内核子系统。
关于设备功耗的信息来源在不同平台上可能会有很大的差异。在某些情况下,可以使用设备树数据来估算这些功耗。在其他情况下,固件可能更清楚。或者,用户空间可能处于最佳位置。为了避免每个客户子系统都重新实现对每种可能的信息来源的支持,EM框架作为一个抽象层介入其中,标准化了内核中功耗表的格式,从而避免了冗余工作。
功耗值可以用微瓦表示,也可以用“抽象刻度”表示。多个子系统可能会使用EM,系统集成者需要检查功耗值刻度类型的要求是否得到满足。例如,可以在《能量感知调度》文档中找到一个例子。对于一些子系统,比如热管理或功耗限制,用“抽象刻度”表示的功耗值可能会引起问题。这些子系统更感兴趣的是过去使用的功耗估计,因此可能需要实际的微瓦值。可以在《功率分配智能功率分配器管理器可调参数》中找到这些要求的例子。内核子系统可能会实现自动检测,以检查EM注册设备是否具有不一致的刻度(基于EM内部标志)。需要牢记的重要一点是,当功耗值用“抽象刻度”表示时,将无法推导出微焦耳的实际能量。
下图描述了一个例子,其中驱动程序(这里是特定于Arm的,但这种方法适用于任何架构)向EM框架提供功耗成本,而感兴趣的客户从中读取数据:
+---------------+ +-----------------+ +---------------+ | Thermal (IPA) | | Scheduler (EAS) | | Other | +---------------+ +-----------------+ +---------------+ | | em_cpu_energy() | | | em_cpu_get() | +---------+ | +---------+ | | | v v v +---------------------+ | Energy Model | | Framework | +---------------------+ ^ ^ ^ | | | em_dev_register_perf_domain() +----------+ | +---------+ | | | +---------------+ +---------------+ +--------------+ | cpufreq-dt | | arm_scmi | | Other | +---------------+ +---------------+ +--------------+ ^ ^ ^ | | | +--------------+ +---------------+ +--------------+ | Device Tree | | Firmware | | ? | +--------------+ +---------------+ +--------------+
对于CPU设备,EM框架管理系统中每个“性能域”的功耗表。性能域是一组性能一起调整的CPU。性能域通常与CPUFreq策略一一对应。性能域中的所有CPU都必须具有相同的微体系结构。不同性能域中的CPU可能具有不同的微体系结构。
2. 核心API
2.1 配置选项
必须启用CONFIG_ENERGY_MODEL才能使用EM框架。
2.2 注册性能域
注册“高级”EM
“高级”EM之所以得名,是因为驱动程序被允许提供更精确的功耗模型。它不受框架中实现的数学公式的限制(就像在“简单”EM情况下一样)。它可以更好地反映为每个性能状态执行的实际功耗测量。因此,在考虑EM静态功耗(泄漏)重要时,应优先考虑使用此注册方法。
驱动程序应通过调用以下API将性能域注册到EM框架中:
int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, struct em_data_callback *cb, cpumask_t *cpus, bool microwatts);
驱动程序必须提供一个回调函数,为每个性能状态返回<频率,功耗>元组。驱动程序提供的回调函数可以自由地从任何相关位置(设备树、固件等)获取数据,并以任何被认为必要的方式获取数据。仅对于CPU设备,驱动程序必须使用cpumask指定性能域的CPU。对于非CPU设备,最后一个参数必须设置为NULL。“microwatts”这个最后一个参数的设置非常重要。使用EM的内核子系统可能依赖于此标志来检查所有EM设备是否使用相同的刻度。如果存在不同的刻度,这些子系统可能决定返回警告/错误、停止工作或触发内核崩溃。有关实现此回调的驱动程序的示例,请参见第3节,或者参见第2.4节以获取有关此API的进一步文档。
EM注册使用DT
EM也可以使用OPP框架和DT中的信息"operating-points-v2"进行注册。DT中的每个OPP条目都可以扩展为包含微瓦特功率值的属性"opp-microwatt"。这个OPP DT属性允许平台注册反映总功率(静态 + 动态)的EM功率值。这些功率值可能直接来自实验和测量。
注册'人工' EM
对于缺乏有关每个性能状态的功率值的详细知识的驱动程序,可以提供自定义回调选项。回调.get_cost()
是可选的,并提供了EAS使用的'cost'值。这对于仅提供CPU类型之间相对效率信息的平台非常有用,其中可以使用该信息创建抽象功率模型。但是,即使是抽象功率模型有时也很难适应输入功率值大小限制。.get_cost()
允许提供反映CPU效率的'cost'值。这将允许提供具有与EM内部公式计算'cost'值所强制的不同关系的EAS信息。要为这样的平台注册EM,驱动程序必须将标志'microwatts'设置为0,提供.get_power()
回调和提供.get_cost()
回调。EM框架将在注册期间正确处理这样的平台。为这样的平台设置了标志EM_PERF_DOMAIN_ARTIFICIAL。其他使用EM的框架需要特别小心地测试和正确处理此标志。
注册'简单' EM
使用框架辅助函数cpufreq_register_em_with_opp()
注册了'简单' EM。它实现了一个与数学公式紧密相关的功率模型:
Power = C * V^2 * f
使用这种方法注册的EM可能无法正确反映真实设备的物理情况,例如静态功率(泄漏)很重要。
2.3 访问性能域
有两个API函数提供对能量模型的访问:em_cpu_get()
以CPU ID作为参数,并且em_pd_get()
以设备指针作为参数。它取决于子系统将使用哪个接口,但在CPU设备的情况下,这两个函数返回相同的性能域。
对于对CPU的能量模型感兴趣的子系统,可以使用em_cpu_get()
API检索它。能量模型表在创建性能域时分配一次,并保持在内存中不变。
可以使用em_cpu_energy()
API估算性能域消耗的能量。在CPU设备的情况下,估算是在假定使用schedutil CPUfreq调度器的情况下进行的。目前,对于其他类型的设备,尚未提供此计算。
有关上述API的更多详细信息可以在<linux/energy_model.h>或第2.4节中找到。
2.4 API描述
https://www.kernel.org/doc/html/v6.6/power/energy-model.html#description-details-of-this-api
3. 示例驱动程序
CPUFreq框架支持专用的回调函数,用于为给定的CPU(s) 'policy'对象注册EM:cpufreq_driver::register_em()。对于给定的驱动程序,必须正确实现该回调函数,因为框架会在设置过程中的适当时间调用它。本节提供了一个简单的示例,展示了一个CPUFreq驱动程序如何使用(虚假的)'foo'协议在能量模型框架中注册性能域。该驱动程序实现了一个est_power()函数,用于提供给EM框架使用:
-> drivers/cpufreq/foo_cpufreq.c 01 static int est_power(struct device *dev, unsigned long *mW, 02 unsigned long *KHz) 03 { 04 long freq, power; 05 06 /* Use the 'foo' protocol to ceil the frequency */ 07 freq = foo_get_freq_ceil(dev, *KHz); 08 if (freq < 0); 09 return freq; 10 11 /* Estimate the power cost for the dev at the relevant freq. */ 12 power = foo_estimate_power(dev, freq); 13 if (power < 0); 14 return power; 15 16 /* Return the values to the EM framework */ 17 *mW = power; 18 *KHz = freq; 19 20 return 0; 21 } 22 23 static void foo_cpufreq_register_em(struct cpufreq_policy *policy) 24 { 25 struct em_data_callback em_cb = EM_DATA_CB(est_power); 26 struct device *cpu_dev; 27 int nr_opp; 28 29 cpu_dev = get_cpu_device(cpumask_first(policy->cpus)); 30 31 /* Find the number of OPPs for this policy */ 32 nr_opp = foo_get_nr_opp(policy); 33 34 /* And register the new performance domain */ 35 em_dev_register_perf_domain(cpu_dev, nr_opp, &em_cb, policy->cpus, 36 true); 37 } 38 39 static struct cpufreq_driver foo_cpufreq_driver = { 40 .register_em = foo_cpufreq_register_em, 41 };