工作性能点 (OPP) 库¶
2009-2010 Nishanth Menon <nm@ti.com>, Texas Instruments Incorporated
1. 简介¶
1.1 什么是工作性能点 (OPP)?¶
当今复杂的 SoC 由多个协同工作的子模块组成。在执行各种用例的运行系统中,并非 SoC 中的所有模块都需要始终以其最高的性能频率运行。为了方便这一点,SoC 中的子模块被分组到域中,允许一些域以较低的电压和频率运行,而其他域则以较高的电压/频率对运行。
设备将为每个域支持的离散频率和电压对的集合称为工作性能点或 OPP。
例如
让我们考虑一个支持以下内容的 MPU 设备:{300MHz,最低电压为 1V},{800MHz,最低电压为 1.2V},{1GHz,最低电压为 1.3V}
我们可以将这些表示为以下 {Hz, uV} 元组的三个 OPP
{300000000, 1000000}
{800000000, 1200000}
{1000000000, 1300000}
1.2 工作性能点库¶
OPP 库提供了一组辅助函数来组织和查询 OPP 信息。该库位于 drivers/opp/ 目录中,头文件位于 include/linux/pm_opp.h 中。可以通过从电源管理 menuconfig 菜单启用 CONFIG_PM_OPP 来启用 OPP 库。某些 SoC(例如德州仪器的 OMAP 框架)允许在不需要 cpufreq 的情况下选择在某个 OPP 处启动。
OPP 库的典型用法如下
(users) -> registers a set of default OPPs -> (library)
SoC framework -> modifies on required cases certain OPPs -> OPP layer
-> queries to search/retrieve information ->
OPP 层期望每个域都由唯一的设备指针表示。SoC 框架使用 OPP 层为每个设备注册一组初始 OPP。此列表预计是一个最佳的小数字,通常每个设备约为 5 个。此初始列表包含框架期望在系统中默认安全启用的一组 OPP。
关于 OPP 可用性的说明¶
随着系统的运行,SoC 框架可能会根据各种外部因素选择在每个设备上使某些 OPP 可用或不可用。示例用法:热管理或其他特殊情况,其中 SoC 框架可能会选择禁用较高的频率 OPP,以便在可以重新启用该 OPP 之前安全地继续操作(如果可能)。
OPP 库在其实现中促进了这一概念。以下操作函数仅在可用的 opps 上运行:dev_pm_opp_find_freq_{ceil, floor}、dev_pm_opp_get_voltage、dev_pm_opp_get_freq、dev_pm_opp_get_opp_count。
dev_pm_opp_find_freq_exact 旨在用于查找 opp 指针,然后可将其用于 dev_pm_opp_enable/disable 函数,以根据需要使 opp 可用。
警告:如果为设备调用了 dev_pm_opp_enable/disable 函数,则 OPP 库的用户应使用 get_opp_count 刷新其可用性计数,触发这些函数的具体机制或向其他依赖子系统(如 cpufreq)的通知机制留给使用 OPP 库的 SoC 特定框架自行决定。在这些操作的情况下,还需要注意刷新 cpufreq 表。
2. 初始 OPP 列表注册¶
SoC 实现迭代调用 dev_pm_opp_add 函数以添加每个设备的 OPP。预计 SoC 框架将注册最佳 OPP 条目 - 典型数字范围应小于 5。注册 OPP 生成的列表由 OPP 库在设备运行期间维护。SoC 框架随后可以使用 dev_pm_opp_enable / disable 函数动态控制 OPP 的可用性。
- dev_pm_opp_add
为由设备指针表示的特定域添加新的 OPP。OPP 使用频率和电压定义。添加后,OPP 被假定为可用,并且可以使用 dev_pm_opp_enable/disable 函数控制其可用性。OPP 库在内部存储和管理 dev_pm_opp 结构中的此信息。SoC 框架可以使用此函数根据 SoC 使用环境的需求定义最佳列表。
- 警告
请勿在中断上下文中使用此函数。
示例
soc_pm_init() { /* Do things */ r = dev_pm_opp_add(mpu_dev, 1000000, 900000); if (!r) { pr_err("%s: unable to register mpu opp(%d)\n", r); goto no_cpufreq; } /* Do cpufreq things */ no_cpufreq: /* Do remaining things */ }
3. OPP 搜索函数¶
诸如 cpufreq 之类的高级框架在频率上运行。为了将频率映射回相应的 OPP,OPP 库提供了方便的函数来搜索 OPP 库内部管理的 OPP 列表。如果找到匹配项,这些搜索函数将返回表示 opp 的匹配指针,否则返回错误。这些错误应通过标准错误检查(如IS_ERR()
)来处理,并且调用者应采取适当的操作。
这些函数的调用者在使用 OPP 后应调用 dev_pm_opp_put()。否则,OPP 的内存将永远不会被释放,从而导致内存泄漏。
- dev_pm_opp_find_freq_exact
基于确切频率和可用性搜索 OPP。此函数对于启用默认不可用的 OPP 特别有用。示例:在 SoC 框架检测到可以提供较高频率的情况下,它可以使用此函数在调用 dev_pm_opp_enable 之前找到 OPP,从而使其真正可用
opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false); dev_pm_opp_put(opp); /* dont operate on the pointer.. just do a sanity check.. */ if (IS_ERR(opp)) { pr_err("frequency not disabled!\n"); /* trigger appropriate actions.. */ } else { dev_pm_opp_enable(dev,1000000000); }
- 注意
这是唯一在不可用的 OPP 上运行的搜索函数。
- dev_pm_opp_find_freq_floor
搜索至多为提供的频率的可用 OPP。此函数在搜索较小的匹配项或按频率递减的顺序处理 OPP 信息时很有用。示例:查找设备的最高 opp
freq = ULONG_MAX; opp = dev_pm_opp_find_freq_floor(dev, &freq); dev_pm_opp_put(opp);
- dev_pm_opp_find_freq_ceil
搜索至少为提供的频率的可用 OPP。此函数在搜索更高的匹配项或按频率递增的顺序处理 OPP 信息时很有用。示例 1:查找设备的最低 opp
freq = 0; opp = dev_pm_opp_find_freq_ceil(dev, &freq); dev_pm_opp_put(opp);
示例 2:SoC cpufreq_driver->target 的简化实现
soc_cpufreq_target(..) { /* Do stuff like policy checks etc. */ /* Find the best frequency match for the req */ opp = dev_pm_opp_find_freq_ceil(dev, &freq); dev_pm_opp_put(opp); if (!IS_ERR(opp)) soc_switch_to_freq_voltage(freq); else /* do something when we can't satisfy the req */ /* do other stuff */ }
4. OPP 可用性控制函数¶
在 OPP 库中注册的默认 OPP 列表可能无法满足所有可能的情况。OPP 库提供了一组函数来修改 OPP 列表中 OPP 的可用性。这允许 SoC 框架对哪些 OPP 集在运行中可用进行细粒度的动态控制。这些函数旨在在诸如热考虑的情况下临时删除 OPP(例如,在温度下降之前不要使用 OPPx)。
- 警告
请勿在中断上下文中使用这些函数。
- dev_pm_opp_enable
使 OPP 可用于操作。示例:假设仅当 SoC 温度低于某个阈值时,才使 1GHz OPP 可用。SoC 框架实现可能会选择执行以下操作
if (cur_temp < temp_low_thresh) { /* Enable 1GHz if it was disabled */ opp = dev_pm_opp_find_freq_exact(dev, 1000000000, false); dev_pm_opp_put(opp); /* just error check */ if (!IS_ERR(opp)) ret = dev_pm_opp_enable(dev, 1000000000); else goto try_something_else; }
- dev_pm_opp_disable
使 OPP 不可用于操作示例:假设如果温度超过阈值,则禁用 1GHz OPP。SoC 框架实现可能会选择执行以下操作
if (cur_temp > temp_high_thresh) { /* Disable 1GHz if it was enabled */ opp = dev_pm_opp_find_freq_exact(dev, 1000000000, true); dev_pm_opp_put(opp); /* just error check */ if (!IS_ERR(opp)) ret = dev_pm_opp_disable(dev, 1000000000); else goto try_something_else; }
5. OPP 数据检索函数¶
由于 OPP 库抽象了 OPP 信息,因此有必要提供一组函数来从 dev_pm_opp 结构中提取信息。一旦使用搜索函数检索到 OPP 指针,SoC 框架就可以使用以下函数来检索 OPP 层内部表示的信息。
- dev_pm_opp_get_voltage
检索 opp 指针表示的电压。示例:在 cpufreq 转换为不同频率时,SoC 框架需要使用稳压器框架将 OPP 表示的电压设置为提供电压的电源管理芯片
soc_switch_to_freq_voltage(freq) { /* do things */ opp = dev_pm_opp_find_freq_ceil(dev, &freq); v = dev_pm_opp_get_voltage(opp); dev_pm_opp_put(opp); if (v) regulator_set_voltage(.., v); /* do other things */ }
- dev_pm_opp_get_freq
检索 opp 指针表示的频率。示例:假设 SoC 框架使用几个辅助函数,我们可以传递 opp 指针,而不是传递其他参数来处理相当多的数据参数
soc_cpufreq_target(..) { /* do things.. */ max_freq = ULONG_MAX; max_opp = dev_pm_opp_find_freq_floor(dev,&max_freq); requested_opp = dev_pm_opp_find_freq_ceil(dev,&freq); if (!IS_ERR(max_opp) && !IS_ERR(requested_opp)) r = soc_test_validity(max_opp, requested_opp); dev_pm_opp_put(max_opp); dev_pm_opp_put(requested_opp); /* do other things */ } soc_test_validity(..) { if(dev_pm_opp_get_voltage(max_opp) < dev_pm_opp_get_voltage(requested_opp)) return -EINVAL; if(dev_pm_opp_get_freq(max_opp) < dev_pm_opp_get_freq(requested_opp)) return -EINVAL; /* do things.. */ }
- dev_pm_opp_get_opp_count
检索设备的可用 opp 数量 示例:假设 SoC 中的协处理器需要知道表中的可用频率,则主处理器可以通知如下
soc_notify_coproc_available_frequencies() { /* Do things */ num_available = dev_pm_opp_get_opp_count(dev); speeds = kcalloc(num_available, sizeof(u32), GFP_KERNEL); /* populate the table in increasing order */ freq = 0; while (!IS_ERR(opp = dev_pm_opp_find_freq_ceil(dev, &freq))) { speeds[i] = freq; freq++; i++; dev_pm_opp_put(opp); } soc_notify_coproc(AVAILABLE_FREQs, speeds, num_available); /* Do other things */ }
6. 数据结构¶
通常,SoC 包含多个可变的电压域。每个域由一个设备指针表示。与 OPP 的关系可以表示如下
SoC
|- device 1
| |- opp 1 (availability, freq, voltage)
| |- opp 2 ..
... ...
| `- opp n ..
|- device 2
...
`- device m
OPP 库维护一个内部列表,SoC 框架会填充该列表,并由上述各种函数访问。但是,表示实际 OPP 和域的结构是 OPP 库内部的,以便允许跨系统的合适抽象可重用性。
- struct dev_pm_opp
OPP 库的内部数据结构,用于表示一个 OPP。除了频率、电压和可用性信息外,它还包含 OPP 库操作所需的内部簿记信息。指向此结构的指针会返回给用户(例如 SoC 框架),作为与 OPP 层交互时 OPP 的标识符。
- 警告
用户不应解析或修改 struct dev_pm_opp 指针。实例的默认值由 dev_pm_opp_add 填充,但 OPP 的可用性可以通过 dev_pm_opp_enable/disable 函数修改。
- struct device
此结构用于向 OPP 层标识一个域。设备的性质及其实现留给 OPP 库的用户(例如 SoC 框架)来决定。
总而言之,在一个简单的视图中,数据结构操作表示如下:
Initialization / modification:
+-----+ /- dev_pm_opp_enable
dev_pm_opp_add --> | opp | <-------
| +-----+ \- dev_pm_opp_disable
\-------> domain_info(device)
Search functions:
/-- dev_pm_opp_find_freq_ceil ---\ +-----+
domain_info<---- dev_pm_opp_find_freq_exact -----> | opp |
\-- dev_pm_opp_find_freq_floor ---/ +-----+
Retrieval functions:
+-----+ /- dev_pm_opp_get_voltage
| opp | <---
+-----+ \- dev_pm_opp_get_freq
domain_info <- dev_pm_opp_get_opp_count