linux opp 模块
目录
- 一、Linux OPP 介绍
- 二、关键数据结构
- dev_pm_opp
- opp_table
- 三、OPP 的 dts 结构
- 3.1 operating-points
- 3.2 operating-points-v2
- opp table dts 属性
- opp dts 属性
- 四、关键接口
- 4.1 创建/删除 opp_table
- 4.2 获取 opp_table
- 4.3 获取 opp 相关接口
一、Linux OPP 介绍
在Linux中,OPP(Operating Performance Points)结构是一种用于描述设备性能状态的机制,主要用于电源管理和性能调节,特别是在处理器、GPU和其他硬件设备中。OPP结构允许系统根据负载需求动态调整设备的性能和功耗,以实现更高的能效。
OPP的主要组成部分:
1. 频率:设备在不同性能状态下的工作频率。
2. 电压:相应的工作电压,通常与频率成正比。
3. 功耗:在特定频率和电压下设备的功耗。
OPP的使用场景:
1. 动态频率调节:在设备负载增加时提升频率,以提高性能;在负载降低时降低频率,以节省电能。
2. 热管理:根据设备的温度和功耗情况调整性能状态,以避免过热。
3. 负载平衡:在多核处理器中,可以根据任务需求将负载动态分配给不同核心,并调整其OPP以优化性能和能耗。
实现方式:
1. OPP通常通过设备树(Device Tree)描述,包含每个性能点的频率、电压和功耗信息。
2. Linux内核中的cpufreq、cpupower、dvfs、thermal 等子系统可以管理这些OPP,以实现对设备的频率和电压的动态调整。
通过OPP机制,Linux能够在提供所需性能的同时,有效管理能耗和热量,从而延长设备的使用寿命和电池续航时间。
二、关键数据结构
dev_pm_opp
dev_pm_opp
在 OPP 中表示单个性能点。
struct dev_pm_opp {
struct list_head node;
struct kref kref; // 用于引用计数,确保相关变量在使用期间不被释放,防止内存泄漏。
bool available; // enable/disable
bool dynamic; // 标记动态性能点
bool turbo; // 标记节点为高性能模式
bool suspend; // suspend时是否有效
unsigned int pstate; // 表示该节点的性能状态
unsigned long rate; // 该opp点的频率
unsigned int level; // 该opp项的级别
struct dev_pm_opp_supply *supplies; // power电压信息
unsigned long clock_latency_ns; // 切换到该opp项的时间延时
struct dev_pm_opp **required_opps; // 指针数组,指向该opp项依赖的其它opp项
struct opp_table *opp_table; // 包含该opp项的opp表
struct device_node *np;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
#endif
};
opp_table
opp_table
负责管理一个设备的 OPP 表,一个 opp 表由多个dev_pm_opp表示的 opp 性能节点组成。
struct opp_table {
struct list_head node;
struct blocking_notifier_head head; //注册管理OPP状态变化时的通知回调
struct list_head dev_list; // 包含该opp table的设备列表
struct list_head opp_list; // 包含所有dev_pm_opp的列表
struct kref kref;
struct kref list_kref; // 引用计数
struct mutex lock;
struct device_node *np;
unsigned long clock_latency_ns_max; // 表示设备在切换opp时最大的延迟
/* For backward compatibility with v1 bindings */
unsigned int voltage_tolerance_v1; // 为向后兼容v1保留的电压容忍度(波动范围)
bool parsed_static_opps; // 指示opp表节点是否已解析
enum opp_table_access shared_opp; // 是否共享
struct dev_pm_opp *suspend_opp; // 指向suspend情况下使用的opp
struct mutex genpd_virt_dev_lock; // 保护虚拟设备的访问
struct device **genpd_virt_devs; // 与opp表关联的虚拟设备数组
struct opp_table **required_opp_tables; // 依赖的其它opp表
unsigned int required_opp_count;
unsigned int *supported_hw; // 支持的硬件配置(dts中配置支持的硬件表示符)
unsigned int supported_hw_count;
const char *prop_name;
struct clk *clk; // 与opp相关的时钟
struct regulator **regulators; // 与opp表相关的电压调节器
int regulator_count;
bool genpd_performance_state;
bool is_genpd;
int (*set_opp)(struct dev_pm_set_opp_data *data); // 指向调整opp的函数指针
struct dev_pm_set_opp_data *set_opp_data; // 设置opp所需要的数据
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
char dentry_name[NAME_MAX];
#endif
};
三、OPP 的 dts 结构
参考Documentation\devicetree\bindings\opp\opp.txt。
在设备树中,OPP(Operating Performance Points)的结构主要用于描述设备的性能状态,包括频率、电压等信息。OPP的DTS结构通常包含多个OPP节点,每个节点对应一个性能点,通常在设备的设备树节点下定义。
3.1 operating-points
OPP dts 的 v1 版本结构,仅支持电压-频率对,节点名为 operating-points,节点包含了频率和电压的组合。
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
next-level-cache = <&L2>;
operating-points = <
/* kHz uV */
792000 1100000
396000 950000
198000 850000
>;
};
3.2 operating-points-v2
OPP dts 的 v2 版本结构,支持更复杂的电压-频率组合。
opp table dts 属性
opp table的dts中可能包含以下属性节点:
- compatible:必须是 “operating-points-v2”;
- opp-shared:可选属性,当 opp table 的 dts 包含该属性时,表示该 opp table 会影响所有使用该 opp table 的设备,任意的 opp 切换会作用到所有的设备上;
- opp 节点;
opp dts 属性
每个opp节点的dts,可能包含以下属性:
- opp-microvolt:电压,uv。size 为 1 或者 3 的数组,size 为 1 时表示,size 为 3 时表示;
- opp-microvolt-:特殊命名的opp-microvolt,可以为一个 opp 提供多个不同属性的电压值,每个电压值可能对应了不同的运行模式,例如 opp-microvolt-slow、opp-microvolt-fast;
- opp-microamp:该 opp 下,设备最大的电流消耗,只有定义 opp-microvolt 前提下,opp-microamp 才是有意义的;
- opp-microamp-:特殊命名的 opp-microamp;
- opp-level:当前节点的性能等级,方便低功耗和高性能状态时调整设备的频率和电压;
- clock-latency-ns:从任何 opp 切换到该 opp 所需要的耗时;
- turbo-mode:指示是否支持短时间内超频;
- opp-suspend:opp 的 dts 包含该属性时,表示当设备 suspend 时,切到该 opp。如果多个 opp 都包含该属性,在 suspend 时会切到最高频率的 opp;
- opp-supported-hw:可选属性,用于指示哪些硬件版本可以支持该 opp,具体的硬件版本号是用户自己定义的;
- required-opps:指示当前 opp 运行时所依赖的其它 opp;
举例
cpu_opp_table: opp_table0 {
compatible = "operating-points-v2";
opp-shared;
opp-1000000000 {
opp-hz = /bits/ 64 <1000000000>;
opp-microvolt = <975000 970000 985000>;
opp-microamp = <70000>;
opp-microvolt-slow = <915000 900000 925000>;
opp-microvolt-fast = <975000 970000 985000>;
opp-microamp-slow = <70000>;
opp-microamp-fast = <71000>;
clock-latency-ns = <300000>;
opp-supported-hw = <0xF 0xFFFFFFFF 0xFFFFFFFF>;
opp-suspend;
};
opp-1100000000 {
opp-hz = /bits/ 64 <1100000000>;
opp-microvolt = <1000000 980000 1010000>;
opp-microamp = <80000>;
clock-latency-ns = <310000>;
opp-supported-hw = <0x20 0xff0000ff 0x0000f4f0>;
opp-microvolt-slow = <915000 900000 925000>, /* Supply vcc0 */
<925000 910000 935000>; /* Supply vcc1 */
opp-microvolt-fast = <975000 970000 985000>, /* Supply vcc0 */
<965000 960000 975000>; /* Supply vcc1 */
opp-microamp = <70000>; /* Will be used for both slow/fast */
};
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <1025000>;
clock-latency-ns = <290000>;
turbo-mode;
};
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
next-level-cache = <&L2>;
clocks = <&clk_controller 0>;
clock-names = "cpu";
cpu-supply = <&cpu_supply0>;
operating-points-v2 = <&cpu0_opp_table>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
next-level-cache = <&L2>;
clocks = <&clk_controller 0>;
clock-names = "cpu";
cpu-supply = <&cpu_supply0>;
operating-points-v2 = <&cpu0_opp_table>;
};
};
四、关键接口
4.1 创建/删除 opp_table
int dev_pm_opp_of_add_table(struct device *dev);
int dev_pm_opp_of_add_table_indexed(struct device *dev, int index);
void dev_pm_opp_of_remove_table(struct device *dev);
以上是部分删除和创建 opp_table 的接口。
dev_pm_opp_of_add_table
接口通过从设备树中解析 opp 信息,并注册 opp_table、添加到设备中。
int dev_pm_opp_of_add_table(struct device *dev)
{
struct opp_table *opp_table;
int ret;
opp_table = dev_pm_opp_get_opp_table_indexed(dev, 0); // 查找或解析dts创建opp_table
if (!opp_table)
return -ENOMEM;
/*
* OPPs have two version of bindings now. Also try the old (v1)
* bindings for backward compatibility with older dtbs.
*/
if (opp_table->np)
ret = _of_add_opp_table_v2(dev, opp_table); // 解析dts,将opp添加到opp_table
else
ret = _of_add_opp_table_v1(dev, opp_table);
if (ret)
dev_pm_opp_put_opp_table(opp_table);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table);
注意,在 _of_add_opp_table_v2
解析 dts 添加 opp 时,会将 opp freq 从低到高的顺序排序(_opp_is_duplicate)然后再添加到 opp_list 中。方便在遍历 list 时,能最有效地找到大于或者小于给定频率的 opp。
dev_pm_opp_get_opp_table_indexed
从已经创建的 opp_table list 查找并返回 opp_table,查找不到的话,则解析 dts 创建 opp_table 并返回。
struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev,
int index)
-> _opp_get_opp_table(dev, index);
-> opp_table = _find_opp_table_unlocked(dev); // 遍历opp_tables和opp_table->dev_list,查找dev的opp_table。查找到的话返回
-> opp_table = _managed_opp(dev, index); // 遍历opp_tables查找opp_table(可能其它设备使用相同的opp_table,已经注册过opp_table), 找到直接返回
-> opp_table = _allocate_opp_table(dev, index); // 申请内存、解析dts创建opp_table
dev_pm_opp_of_add_table_indexed
与 dev_pm_opp_of_add_table
接口类似,初始化指定 index 的 opp_table.
4.2 获取 opp_table
struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev, int index);
dev_pm_opp_get_opp_table
接口会调用 _opp_get_opp_table
接口,前面已经讲过,_opp_get_opp_table
接口会从已经创建的 opp_table list 查找并返回 opp_table,查找不到的话,则解析 dts 创建 opp_table 并返回。
struct opp_table *dev_pm_opp_get_opp_table(struct device *dev)
{
return _opp_get_opp_table(dev, 0);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_table);
dev_pm_opp_get_opp_table_indexed 接口类似。
4.3 获取 opp 相关接口
// 根据freq和available精确的查找opp
struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
unsigned long freq,
bool available);
// 根据level,精确度查找opp,level通过dts中的“opp-level"定义
struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev,
unsigned int level);
// 返回小于或等于freq的最高opp,并将opp的freq赋给freq
struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
unsigned long *freq);
// 根据给定volt查找opp频率上限(大于等于指定打压的最小频率)
struct dev_pm_opp *dev_pm_opp_find_freq_ceil_by_volt(struct device *dev,
unsigned long u_volt);
// 根据给定的freq查找opp表中大于或等于freq的最小频率。
struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
unsigned long *freq);
例如:
在驱动程序中,根据不同条件查找确定合适的 opp,方便进一步调频等动作。
unsigned long target_freq = 1200000; // 1.2 GHz
struct dev_pm_opp *opp = dev_pm_opp_find_freq_ceil(dev, &target_freq);
if (!IS_ERR(opp)) {
// 找到了合适的 OPP,可以获取其频率等信息
unsigned long freq = opp->rate;
// 使用频率...
dev_pm_opp_put(opp); // 减少引用计数
} else {
// 处理未找到 OPP 的情况
}