目录
- 一、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/disablebool 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_FSstruct 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情况下使用的oppstruct 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_FSstruct 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 1100000396000 950000198000 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_tableif (!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_tableelseret = _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 的情况
}