概述

1.  什么是Operating Performance Points?
如今复杂的Soc由多个并行工作的子模块组成。在一个执行各种用例的操作系统中,不是Soc中的所有模块都一直以其最高的执行频率工作。为了实现这一目的,Soc中的子模块被分组成域,允许一些域以较低的频率和电压运行,而其他的域运行在较高的电压和频率上。将域中每个设备支持的电压和频率的离散元组的集合称为Operating Performance Points(OPP)。
举例如下:
假设一个MPU设备支持如下的电压和频率关系:
{300MHz at minimum voltage of 1V}
{800MHz at minimum voltage of 1.2V}
{1GHz at minimum voltage of 1.3V}
用OPP表示就可以用{Hz,  uV}方式表示如下:
{300000000, 1000000} 
{800000000, 1200000}
{1000000000, 1300000}
2.  Operating Performance Points Library
OPP library提供了一系列辅助函数去管理和查询设备的OPP信息。OPP library的源代码路径在drivers/base/power/opp.c,头文件路径在include/linux/pm_opp.h中。OPP library功能可以通过kernel config:  CONFOG_PM_OPP去使能。
opp library的典型用法如下:
a.  用户为设备(比如CPU)配置/注册一些默认的opp信息。
b.  Soc会根据具体的运行情况,通过opp层去改变/查询设备的opp信息。

数据结构

Linux系统使用struct dev_pm_opp结构表示一个opp描述结构
struct dev_pm_opp {struct list_head node;bool available;unsigned long rate;unsigned long u_volt;struct device_opp *dev_opp;struct rcu_head head;
};

node:         用于链表管理此设备下的opp。

available:   用于判断此opp使能可以使用。
rate:           频率,单位Hz
u_volt:       电压。
dev_opp:   struct device_opp类型指针,指向此opp所属的设备。
Linux系统使用struct device_opp结构表示opp设备。
struct device_opp {struct list_head node;struct device *dev;struct srcu_notifier_head head;struct list_head opp_list;
};

.node:       用于将所有的opp设备使用dev_opp_list链表管理。

.dev:         设备指针。
.head:       opp设备的通知链。
.opp_list:  opp设备具有的opp数据信息。

opp layer数据组织格式

Internal data structure organization with the OPP layer library is as follows:
dev_opp_list (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 dev_opp structure while each opp is represented by the opp structure.

可以看到所有的opp设备使用dev_opp_list组织,在每个opp设备(struct dev_opp)中存在着不同的opp数据信息,这些opp信息使用struce dev_pm_opp表示。这样就形成一个树状的结构。查找合适的opp信息,可以从树的root节点查起。

opp library维护一张内部的列表,Soc框架只需要填充和访问opp数据。但是struct dev_opp和struct dev_pm_opp结构只是在opp library内部使用,外部不需要关心其内部。

API接口说明

  • 添加一个opp table(dev_pm_opp_add)
int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
{struct device_opp *dev_opp = NULL;struct dev_pm_opp *opp, *new_opp;struct list_head *head;/* allocate new OPP node */new_opp = kzalloc(sizeof(*new_opp), GFP_KERNEL);if (!new_opp) {dev_warn(dev, "%s: Unable to create new OPP node\n", __func__);return -ENOMEM;}/* Hold our list modification lock here */mutex_lock(&dev_opp_list_lock);/* Check for existing list for 'dev' */dev_opp = find_device_opp(dev);if (IS_ERR(dev_opp)) {/** Allocate a new device OPP table. In the infrequent case* where a new device is needed to be added, we pay this* penalty.*/dev_opp = kzalloc(sizeof(struct device_opp), GFP_KERNEL);if (!dev_opp) {mutex_unlock(&dev_opp_list_lock);kfree(new_opp);dev_warn(dev,"%s: Unable to create device OPP structure\n",__func__);return -ENOMEM;}dev_opp->dev = dev;srcu_init_notifier_head(&dev_opp->head);INIT_LIST_HEAD(&dev_opp->opp_list);/* Secure the device list modification */list_add_rcu(&dev_opp->node, &dev_opp_list);}/* populate the opp table */new_opp->dev_opp = dev_opp;new_opp->rate = freq;new_opp->u_volt = u_volt;new_opp->available = true;/** Insert new OPP in order of increasing frequency* and discard if already present*/head = &dev_opp->opp_list;list_for_each_entry_rcu(opp, &dev_opp->opp_list, node) {if (new_opp->rate <= opp->rate)break;elsehead = &opp->node;}/* Duplicate OPPs ? */if (new_opp->rate == opp->rate) {int ret = opp->available && new_opp->u_volt == opp->u_volt ?0 : -EEXIST;dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",__func__, opp->rate, opp->u_volt, opp->available,new_opp->rate, new_opp->u_volt, new_opp->available);mutex_unlock(&dev_opp_list_lock);kfree(new_opp);return ret;}list_add_rcu(&new_opp->node, head);mutex_unlock(&dev_opp_list_lock);/** Notify the changes in the availability of the operable* frequency/voltage list.*/srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ADD, new_opp);return 0;
}

此函数有三个参数,第一个参数是为那个设备添加oppinfo,第二个是频率,第三个是电压值。

1.   分配一个新的dev_pm_opp结构,用于存放传入的频率和电压数据。
2.   通过find_device_opp函数在dev_opp_list链表中查找此设备是否已经注册。如果注册直接将opp info添加到设备中。如果没有注册则重新分配一个struct device_opp结构,进行一系列初始化。
3.   填充opp数据,包括电压,频率,是否使能,所属的设备。
4.   按照递增的顺序插入opp数据到opp table中,如果插入的opp数据已经存在返回相应的结果。
5.   调用通知链,通知有新的opp添加进来。
  • opp 查询相关接口
  • dev_pm_opp_find_freq_exact(返回指定freq的opp)
  • dev_pm_opp_find_freq_floor(返回小于或者等于指定freq的opp,返回时从参数freq中返回实际获取的freq)
  • dev_pm_opp_find_freq_ceil (返回大于或者等于指定freq的opp,返回时从参数freq中返回实际获取的freq)
此三个函数大致一样,不过dev_pm_opp_find_freq_exact函数会根据参数available,返回处于disable的opp。而剩余的两个函数只返回enable的opp。
struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,unsigned long *freq)
{struct device_opp *dev_opp;struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);if (!dev || !freq) {dev_err(dev, "%s: Invalid argument freq=%p\n", __func__, freq);return ERR_PTR(-EINVAL);}dev_opp = find_device_opp(dev);if (IS_ERR(dev_opp))return ERR_CAST(dev_opp);list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {if (temp_opp->available && temp_opp->rate >= *freq) {opp = temp_opp;*freq = opp->rate;break;}}return opp;
}

1.   得opp的设备。如果没有返回错误

2.   从dev_opp的opp_list中逐一判断,如果此频率大于等于传入的参数freq,则返回此opp。同时通过参数freq返回opp的rate值。
  • 使能/禁止设备的opp(dev_pm_opp_enable/dev_pm_opp_disable)
static int opp_set_availability(struct device *dev, unsigned long freq,bool availability_req)
{struct device_opp *tmp_dev_opp, *dev_opp = ERR_PTR(-ENODEV);struct dev_pm_opp *new_opp, *tmp_opp, *opp = ERR_PTR(-ENODEV);int r = 0;/* keep the node allocated */new_opp = kmalloc(sizeof(*new_opp), GFP_KERNEL);if (!new_opp) {dev_warn(dev, "%s: Unable to create OPP\n", __func__);return -ENOMEM;}mutex_lock(&dev_opp_list_lock);/* Find the device_opp */list_for_each_entry(tmp_dev_opp, &dev_opp_list, node) {if (dev == tmp_dev_opp->dev) {dev_opp = tmp_dev_opp;break;}}if (IS_ERR(dev_opp)) {r = PTR_ERR(dev_opp);dev_warn(dev, "%s: Device OPP not found (%d)\n", __func__, r);goto unlock;}/* Do we have the frequency? */list_for_each_entry(tmp_opp, &dev_opp->opp_list, node) {if (tmp_opp->rate == freq) {opp = tmp_opp;break;}}if (IS_ERR(opp)) {r = PTR_ERR(opp);goto unlock;}/* Is update really needed? */if (opp->available == availability_req)goto unlock;/* copy the old data over */*new_opp = *opp;/* plug in new node */new_opp->available = availability_req;list_replace_rcu(&opp->node, &new_opp->node);mutex_unlock(&dev_opp_list_lock);kfree_rcu(opp, head);/* Notify the change of the OPP availability */if (availability_req)srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ENABLE,new_opp);elsesrcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_DISABLE,new_opp);return 0;unlock:mutex_unlock(&dev_opp_list_lock);kfree(new_opp);return r;
}

通过低三个参数availability_req去判断去enable/disable opp。

1.   分配一个新的dev_pm_opp结构。
2.   从dev_opp_list中找到opp设备,找不到opp设备就返回错误。
3.   从opp_list中通过opp的freq逐一去比较freq的值。如果找到,获得到当前的opp结构。
4.   如果当前opp的状态与传入的状态一致,则退出。
5.   将找到的opp数据拷贝到分配的新的opp中,然后设置opp的状态。
6.   替换新的opp,然后释放旧的opp数据。
7.   根据参数发送相应的OPP_EVENT_ENABLE/OPP_EVENT_DISABLE通知链。
  • of_init_opp_table(从dt中初始化opp table)
先列举一个opp在dt中的格式:

cpu@0 { compatible = "arm,cortex-a9";reg = <0>;next-level-cache = <&L2>;operating-points = < /* kHz    uV */ 792000  1100000 396000  950000 198000  850000 >;
}; 

可以看到有三组频率和电压的组合,使用operating-points节点表示。从dt中解析opp的代码如下:

int of_init_opp_table(struct device *dev)
{const struct property *prop;const __be32 *val;int nr;prop = of_find_property(dev->of_node, "operating-points", NULL);if (!prop)return -ENODEV;if (!prop->value)return -ENODATA;/** Each OPP is a set of tuples consisting of frequency and* voltage like <freq-kHz vol-uV>.*/nr = prop->length / sizeof(u32);if (nr % 2) {dev_err(dev, "%s: Invalid OPP list\n", __func__);return -EINVAL;}val = prop->value;while (nr) {unsigned long freq = be32_to_cpup(val++) * 1000;unsigned long volt = be32_to_cpup(val++);if (dev_pm_opp_add(dev, freq, volt))dev_warn(dev, "%s: Failed to add OPP %ld\n",__func__, freq);nr -= 2;}return 0;
}

找到operation-points节点,调用dev_pm_opp_add函数添加opp数据。

Linux电源管理-Operating Performance Points(OPP)相关推荐

  1. Linux电源管理(1)_整体架构 -- wowo

    1. 前言 在这个世界中,任何系统的运转都需要能量.如树木依靠光能生长,如马儿依靠食物奔跑,如计算机系统依靠电能运行.而能量的获取是有成本的,因此如果能在保证系统运转的基础上,尽量节省对能量的消耗,就 ...

  2. Linux 电源管理子系统

    Linux 在消费电子领域的应用已经相当普遍,而对于消费电子产品而言,省电是一个重要的议题. Linux 电源管理非常复杂,牵扯到系统级的待机.频率电压变换.系统空闲时的处理以及每个设备驱动对系统待机 ...

  3. linux系统电源时钟,linux电源管理的一些梳理

    由于项目产品需要过能源之星3.0,所以最近做了一些电源管理低功耗方面的工作,抽个时间正好梳理一下. 其实Linux 电源管理非常复杂,牵扯到很多方面,比如系统级的待机.频率电压变换.系统空闲时的处理以 ...

  4. Linux电源管理(一)电源管理系统架构

    概述 Linux 电源管理非常复杂,牵扯到系统级的待机.频率电压变换.系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关. 对于消费电 ...

  5. Linux电源管理(10)_autosleep

    Linux电源管理(10)_autosleep 作者:wowo 发布于:2014-9-18 23:42 分类:电源管理子系统 1. 前言 Autosleep也是从Android wakelocks补丁 ...

  6. linux 电源管理 regulator,Linux内核电源管理综述

    资料: http://blog.csdn.net/bingqingsuimeng/article/category/1228414 http://os.chinaunix.net/a2006/0519 ...

  7. Linux电源管理(5)_Hibernate和Sleep功能介绍【转】

    本文转载自:http://www.wowotech.net/pm_subsystem/std_str_func.html 1. 前言 Hibernate和Sleep两个功能是Linux Generic ...

  8. linux 电池管理软件,Linux电源管理(2)_Generic PM之基本概念和软件架构

    Linux电源管理(2)_Generic PM之基本概念和软件架构 作者:wowo 发布于:2014-5-13 19:24 分类:电源管理子系统 1. 前言 这里的Generic PM,是蜗蜗自己起的 ...

  9. Linux电源管理(2)_Generic PM之基本概念和软件架构(蜗窝科技,www.wowotech.net)

    1. 前言 这里的Generic PM,是蜗蜗自己起的名字,指Linux系统中那些常规的电源管理手段,包括关机(Power off).待机(Standby or Hibernate).重启(Reboo ...

  10. 九万字图文讲透彻 Linux 电源管理及实例分析

    九万字图文讲透彻 Linux 电源管理及实例分析. 计算机运行在物理世界中,物理世界中的一切活动都需要消耗能量.能量的形式有很多种,如热能.核能.化学能等.计算机消耗的是电能,其来源是电池或者外电源. ...

最新文章

  1. 如何搭建一个功能复杂的前端配置化框架(一)
  2. android调试更换模拟器,在模拟器上调试 Android 磨损
  3. 详解 Python 源码之对象机制
  4. 金融风控实战——集成学习
  5. 返璞归真的Linux BFS调度器
  6. Android屏幕适配全攻略(最权威的官方适配指导)(转),共大家分享。
  7. VNC怎么和宿主机共享粘贴板(整理)
  8. 电脑忽然卡了,键盘鼠标也失灵,问题所在,如何处理?
  9. vue 获取元素在浏览器的位置_前端开发JS获取页面元素的位置
  10. spring mvc 思想
  11. 苹果android怎么升级,微信系统升级!苹果安卓手机如何升级更新为最新版微信8.0?...
  12. shell命令行快捷键
  13. 开机提示对话框“位置不可用”
  14. Python: Flask后端与webapi
  15. 深圳学校积分计算机,深圳积分入户计算机职称 能加分吗,很多人都不知道这个!...
  16. 户外运动手持GPS设备常识汇总
  17. java数据类型 枚举_枚举(enum)属于原始数据类型(primitive typ
  18. JS-part12.3-ES6- 箭头函数 / 函数的参数默认值 / 模板字符串 / 点点点运算符 / 解构赋值 / 对象的简写形式
  19. 加盟汉庭酒店,后疫情时代稳健的投资方式
  20. 普通话水平测试软件异错词,最新普通话水平测试易错词语

热门文章

  1. css的部分应用示例
  2. Bridge(桥接)-对象结构型模式
  3. apache配置Options详解
  4. 编译与运行、解释程序与编译程序
  5. 使用数据库引擎优化顾问添加建议索引
  6. nginx优化配置(转)
  7. WebSocket+HTML5实现在线聊天室
  8. IDC:“互联网+流通”将进一步释放活力
  9. 百度地图KEY发布版SHA1和开发板SHA1如何获得
  10. HDU 2883 kebab(最大流)