英语

工作性能点 (OPP) 库

  1. 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