1. 前言
本文是分析cpufreq framework之前的一篇前置文章,用于介绍Linux电源管理中的Operating Performance Point (OPP)接口。
OPP是一个单纯的软件library,用于归纳、管理各个硬件模块的、可工作的{频率}/ {电压}组合。它不涉及任何硬件,也没有复杂的逻辑,再加上Kernel document(Documentation/power/opp.txt )描述的非常清晰,因此本文只是简单的从功能和API两个方便介绍OPP,不再分析其source code及内部实现逻辑。
2. 功能说明
2.1 什么是OPP
“Documentation/power/opp.txt ”中解释OPP的原话(我翻译了一下)是:
当前复杂的SoCs都包括多个协同工作的子模块。根据具体的应用场景,很多子模块(蜗蜗注:典型的例子是CPU)并不需要一直工作在最高的频率上。因此SoC中的子模块划分为不同的domains,允许一些domains在较低的电压/频率下工作,而另一些在较高的电压/频率下工作。
domain中的设备支持的所有频率和电压的组合,称作Operating Performance Points,简称为OPPs。
注1:为什么一定是频率和电压的组合?因为频率高低决定器件的工作性能,降低性能的目的是节省功耗。但频率对功耗的影响是有限的,而电压对功耗的影响却相当可观。那频率和电压有什么关系呢?通常情况下,当频率降低之后,器件的工作电压也是可以降低的(回忆一下数字电路)。因此不同的“频率/电压”组合,就组成了器件在性能和功耗之间的跷跷板,我们需要做的,就是根据实际场景,选择一个合适的组合。
2.2 使用场景
对具备多个OPP的设备而言,相关的使用场景包括:
1)需要一个三维数组,保存所有的OPPs。
2)可以方便的更改OPP条目。
3)当需要改变设备的OPP时,可以方便的查询设备支持哪些OPP。
4)可以通过一定的条件查询OPP信息,例如以频率值查询、以电压值查询、以频率或者电压范围查询等等。
5)其它需求。
其实蛮简单的,但考虑到这些需求对所有设备(应该也不是很多)都是相同,kernel就抽象出来一个library–就是OPP library,实现上述功能。具体可参考后续的描述。
2.3 实现思路
试下OPP library的主要思路,就是以设备为单位,管理OPP信息。如下(摘录自Documentation/power/opp.rst ):
通常,一个 SoC(System on Chip,芯片上的系统)包含多个可变的电压域。每个域由一个设备指针表示。与 OPP(Operating Performance Points,运行性能点)的关系可以如下表示:
SoC
|- device 1
| |- opp 1 (availability, freq, voltage)
| |- opp 2 ..
... ...
| `- opp n ..
|- device 2
...
`- device m
OPP 库维护一个内部列表,SoC 框架会填充该列表,并由各种上述描述的函数访问。然而,表示实际 OPP 和域的结构是 OPP 库本身的内部结构,以允许适当的抽象可在系统间重复使用。
/*
* Internal data structure organization with the OPP layer library is as
* follows:
* opp_tables (root)
* |- device 1 (represents voltage domain 1)
* | |- opp 1 (availability, freq, voltage)
* | |- opp 2 ..
* ... ...
* | `- opp n ..
* |- device 2 (represents the next voltage domain)
* ...
* `- device m (represents mth voltage domain)
* device 1, 2.. are represented by opp_table structure while each opp
* is represented by the opp structure.
*/
注释:这段注释描述了 OPP 层库的内部数据结构组织方式,包括 OPP 表(opp_tables)作为根,每个设备表示一个电压域,而每个 OPP 结构表示一个 OPP。
/**
* struct dev_pm_opp - Generic OPP description structure
* @node: opp table node. The nodes are maintained throughout the lifetime
* of boot. It is expected only an optimal set of OPPs are
* added to the library by the SoC framework.
* IMPORTANT: the opp nodes should be maintained in increasing
* order.
* @kref: for reference count of the OPP.
* @available: true/false - marks if this OPP as available or not
* @dynamic: not-created from static DT entries.
* @turbo: true if turbo (boost) OPP
* @suspend: true if suspend OPP
* @removed: flag indicating that OPP's reference is dropped by OPP core.
* @pstate: Device's power domain's performance state.
* @rates: Frequencies in hertz
* @level: Performance level
* @supplies: Power supplies voltage/current values
* @bandwidth: Interconnect bandwidth values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
* frequency from any other OPP's frequency.
* @required_opps: List of OPPs that are required by this OPP.
* @opp_table: points back to the opp_table struct this opp belongs to
* @np: OPP's device node.
* @dentry: debugfs dentry pointer (per opp)
*
* This structure stores the OPP information for a given device.
*/
struct dev_pm_opp {
struct list_head node;
struct kref kref;
bool available;
bool dynamic;
bool turbo;
bool suspend;
bool removed;
unsigned int pstate;
unsigned long *rates;
unsigned int level;
struct dev_pm_opp_supply *supplies;
struct dev_pm_opp_icc_bw *bandwidth;
unsigned long clock_latency_ns;
struct dev_pm_opp **required_opps;
struct opp_table *opp_table;
struct device_node *np;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
const char *of_name;
#endif
};
struct dev_pm_opp 是一个通用的 OPP 描述结构,用于存储给定设备的 OPP 信息。
node: OPP 表节点,这些节点在整个引导过程中被维护,预期只有 SoC 框架添加了一组最优的 OPP 到库中。重要的是 OPP 节点应按递增顺序维护。
kref: OPP 的引用计数。
available: 表示此 OPP 是否可用的布尔值。
dynamic: 表示 OPP 是否是通过动态方式创建的,而不是从静态设备树(DT)条目中创建的。
turbo: 如果是 Turbo(提升)OPP,则为 true。
suspend: 如果是挂起 OPP,则为 true。
removed: 表示 OPP 的引用是否被 OPP 核心移除的标志。
pstate: 设备的电源域性能状态。
rates: 频率值的数组(以赫兹为单位)。
level: 性能级别。
supplies: 电源供应的电压/电流值。
bandwidth: 互连带宽值。
clock_latency_ns: 切换到此 OPP 频率时与任何其他 OPP 频率的延迟(以纳秒为单位)。
required_opps: 此 OPP 所需的 OPP 列表。
opp_table: 指向包含此 OPP 的 opp_table 结构的指针。
np: OPP 的设备节点。
dentry: 调试文件系统的 dentry 指针(每个 OPP)。
of_name: 如果配置了调试文件系统,则是 OPP 的设备树名称。
该结构的主要目的是存储 OPP 的相关信息,以便进行电源管理和性能优化。
3. 接口说明
OPP library的source code位于drivers/opp/core.c中,header位于include/linux/pm_opp.h中,提供的接口包括
/**
* dev_pm_opp_add() - 从表定义中添加一个 OPP 表
* @dev: 进行此操作的设备
* @freq: 此 OPP 的频率(赫兹)
* @u_volt: 此 OPP 的电压(微伏)
*
* 此函数将 OPP 定义添加到 OPP 表中并返回状态。默认情况下,此 OPP 可用,并且可以使用 dev_pm_opp_enable/disable 函数进行控制。
*
* 返回值:
* 0 成功时,或
* 重复的 OPP(freq 和 volt 都相同)且 opp->available
* -EEXIST freq 相同但 volt 不同,或
* 重复的 OPP(freq 和 volt 都相同)但 !opp->available
* -ENOMEM 内存分配失败
*/
int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
{
struct opp_table *opp_table;
int ret;
opp_table = _add_opp_table(dev, true);
if (IS_ERR(opp_table))
return PTR_ERR(opp_table);
/* 为动态 OPP 修正调节器计数 */
opp_table->regulator_count = 1;
ret = _opp_add_v1(opp_table, dev, freq, u_volt, true);
if (ret)
dev_pm_opp_put_opp_table(opp_table);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_add);
/**
* dev_pm_opp_enable() - 启用特定的 OPP
* @dev: 进行此操作的设备
* @freq: 要启用的 OPP 频率
*
* 启用提供的 OPP。如果操作有效,则返回 0,否则返回相应的错误值。此函数用于在使用 dev_pm_opp_disable 临时将 OPP 设置为不可用后,使其再次可用。
*
* 返回值:-EINVAL 表示指针错误,-ENOMEM 表示复制操作没有足够内存,返回 0 表示未进行修改或修改成功。
*/
int dev_pm_opp_enable(struct device *dev, unsigned long freq)
{
return _opp_set_availability(dev, freq, true);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_enable);
/**
* dev_pm_opp_disable() - 禁用特定的 OPP
* @dev: 进行此操作的设备
* @freq: 要禁用的 OPP 频率
*
* 禁用提供的 OPP。如果操作有效,则返回 0,否则返回相应的错误值。此函数用于由用户进行临时控制,使该 OPP 在适当的情况下不可用,直到通过 dev_pm_opp_enable 调用再次启用。
*
* 返回值:-EINVAL 表示指针错误,-ENOMEM 表示复制操作没有足够内存,返回 0 表示未进行修改或修改成功。
*/
int dev_pm_opp_disable(struct device *dev, unsigned long freq)
{
return _opp_set_availability(dev, freq, false);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_disable);
具体请查看drivers/opp目录下code。
dts中定义cpu frequency如下所示:
cpu0_opp_table: opp-table-0 {
compatible = "operating-points-v2";
opp-shared;
opp-600000000 {
opp-hz = /bits/ 64 <600000000>;
opp-microvolt = <950000 950000 1350000>;
clock-latency-ns = <40000>;
opp-suspend;
};
opp-816000000 {
opp-hz = /bits/ 64 <816000000>;
opp-microvolt = <1050000 1050000 1350000>;
clock-latency-ns = <40000>;
};
opp-1008000000 {
opp-hz = /bits/ 64 <1008000000>;
opp-microvolt = <1175000 1175000 1350000>;
clock-latency-ns = <40000>;
};
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <1300000 1300000 1350000>;
clock-latency-ns = <40000>;
};
opp-1296000000 {
opp-hz = /bits/ 64 <1296000000>;
opp-microvolt = <1350000 1350000 1350000>;
clock-latency-ns = <40000>;
};
};