Linux电源管理(11)_Runtime PM之功能描述

作者:wowo 发布于:2014-10-8 23:32 分类:电源管理子系统

1. 前言

终于可以写Runtime PM(后面简称RPM)了,说实话,蜗蜗有点小激动。因为从个人的角度讲,我很推崇使用RPM进行日常的动态电源管理,而不是suspend机制。

软件工程的基本思想就是模块化:高内聚和低耦合。通俗地讲呢,就是“各人自扫门前雪”,尽量扫好自己的(高内聚),尽量不和别人交互(低耦合)。而RPM正体现了这一思想:每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。每个设备都是最节省的话,整个系统一定是最节省的,最终达到无所谓睡、无所谓醒的天人合一状态。

讲到这里想到自己的一则趣事:大学时,蜗蜗是寝室长,但不爱打扫卫生,于是就提出一个口号,“不污染,不治理;谁污染,谁治理”。结果呢,大家猜就是了,呵呵。言归正传,开始吧。

2. Runtime PM的软件框架

听多了RPM的传说,有种莫名的恐惧,觉的会很复杂。但看代码,也就是“drivers/base/power/runtime.c”中1400行而已。

从设计思路上讲,它确实简单。下面是一个大概的软件框架:

Runtime PM architecture

device driver(或者driver所在的bus、class等)需要提供3个回调函数,runtime_suspend、runtime_resume和runtime_idle,分别用于suspend device、resume device和idle device。它们一般由RPM core在合适的时机调用,以便降低device的power consumption。

而调用的时机,最终是由device driver决定的。driver会在适当的操作点,调用RPM core提供的put和get系列的helper function,汇报device的当前状态。RPM core会为每个device维护一个引用计数,get时增加计数值,put时减少计数值,当计数为0时,表明device不再被使用,可以立即或一段时间后suspend,以节省功耗。

好吧,说总是简单,那做呢?很不幸,到目前为止,linux kernel的runtime PM还是很复杂。这里的复杂,不是从实现的角度,而是从对外的角度。在“include\linux\pm_runtime.h”中,RPM提供了将近50个接口。软件模块化的设计理念中,最重要的一个原则就是提供简洁的接口。很显然,RPM没有做到!

无论RPM面对的问题有多么复杂,无论理由有多么充分,它也应坚守“简洁性”这一原则。否则,结果只有一个----无人敢用。这就是当前Linux kernel电源管理中“Opportunistic suspend”和RPM两种机制并存的原因。但是,就算现状不理想,也不能否认RPM的先进性,在当前以及未来很长的一段时间内,它会是kernel电源管理更新比较活跃的部分,因为可以做的还很多。

鉴于这个现状,本文以及后续RPM有关的文章,会选取最新的kernel(当前为linux-3.17),以便及时同步相关的更新。

3. Runtime PM的运行机制

3.1 核心机制

RPM的核心机制是这样的:

1)为每个设备维护一个引用计数(device->power.usage_count),用于指示该设备的使用状态。

2)需要使用设备时,device driver调用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用计数;不再使用设备时,device driver调用pm_runtime_put(或pm_runtime_put_sync)接口,减少引用计数。

3)每一次put,RPM core都会判断引用计数的值。如果为零,表示该设备不再使用(idle)了,则使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_idle回调函数。

4).runtime_idle的存在,是为了在idle和suspend之间加一个缓冲,避免频繁的suspend/resume操作。因此它的职责是:判断设备是否具备suspend的条件,如果具备,在合适的时机,suspend设备。

可以不提供,RPM core会使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_suspend回调函数,suspend设备,同时记录设备的PM状态;

可以调用RPM core提供helper函数(pm_runtime_autosuspend_expiration、pm_runtime_autosuspend、pm_request_autosuspend),要求在指定的时间后,suspend设备。

5)pm_runtime_autosuspend、pm_request_autosuspend等接口,会起一个timer,并在timer到期后,使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_suspend回调函数,suspend设备,同时记录设备的PM状态。

6)每一次get,RPM core都会判断设备的PM状态,如果不是active,则会使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_resume回调函数,resume设备。

注1:Runtime PM中的“suspend”,不一定要求设备必须进入低功耗状态,而是要求设备在suspend后,不再处理数据,不再和CPUs、RAM进行任何的交互,直到设备的.runtime_resume被调用。因为此时设备的parent(如bus controller)、CPU是、RAM等,都有可能因为suspend而不再工作,如果设备再有任何动作,都会造成不可预期的异常。下面是“Documentation\power\runtime_pm.txt”中的解释,供大家参考:

* Once the subsystem-level suspend callback (or the driver suspend callback,
  if invoked directly) has completed successfully for the given device, the PM
  core regards the device as suspended, which need not mean that it has been
  put into a low power state.  It is supposed to mean, however, that the
  device will not process data and will not communicate with the CPU(s) and
  RAM until the appropriate resume callback is executed for it.  The runtime
  PM status of a device after successful execution of the suspend callback is
  'suspended'.

注2:回忆一下wakeup eventswakeup lock,Runtime PM和它们在本质上是一样的,都是实时的向PM core报告“我不工作了,可以睡了”、“我要工作了,不能睡(或醒来吧)”。不同的是:wakeup events和RPM的报告者是内核空间drivers,而wakeup lock是用户空间进程;wakeup events和wakelock涉及的睡眠对象是整个系统,包括CPU和所有的devices,而RPM是一个一个独立的device(CPU除外,它由cpu idle模块处理,可看作RPM的特例)。

3.2 get和put的时机

这个话题的本质是:device idle的判断标准是什么?

再回忆一下“autosleep”中有关“Opportunistic suspend”的讨论,对“Opportunistic suspend”而言,suspend时机的判断是相当困难的,因为整机的运行环境比较复杂。而每一个具体设备的idle,就容易多了,这就是Runtime PM的优势。回到这个话题上,对device而言,什么是idle?

device是通过用户程序为用户提供服务的,而服务的方式分为两种:接受指令,做事情(被动);上报事件(主动,一般通过中断的方式)。因此,设备active的时间段,包括【接受指令,完成指令】和【事件到达,由driver记录下来】两个。除此之外的时间,包括driver从用户程序获得指令(以及相关的数据)、driver将事件(以及相关的数据)交给应用程序,都是idle时间。

那idle时间是否应立即suspend以节省功耗?不一定,要具体场景具体对待:例如网络传输,如果网络连接正常,那么在可预期的、很短的时间内,设备又会active(传输网络数据),如果频繁suspend,会降低性能,且不会省电;比如用户按键,具有突发性,因而可以考虑suspend;等等。

由于get和put正是设备idle状态的切换点,因此get和put的时机就容易把握了:

1)主动访问设备时,如写寄存器、发起数据传输等等,get,增加引用计数,告诉RPM core设备active;访问结束后,put,减小引用计数,告诉RPM core设备可能idle。

2)设备有事件通知时,get(可能在中断处理中);driver处理完事件后,put。

注3:以上只是理论场景,实际可以放宽,以减小设计的复杂度。

3.3 异步(ASYNC)和同步(SYNC)

设备驱动代码可在进程和中断两种上下文执行,因此put和get等接口,要么是由用户进程调用,要么是由中断处理函数调用。由于这些接口可能会执行device的.runtime_xxx回调函数,而这些接口的执行时间是不确定的,有些可能还会睡眠等待。这对用户进程或者中断处理函数来说,是不能接受的。

因此,RPM core提供的默认接口(pm_runtime_get/pm_runtime_put等),采用异步调用的方式(由ASYNC flag表示),启动一个work queue,在单独的线程中,调用.runtime_xxx回调函数,这可以保证设备驱动之外的其它模块正常运行。

另外,如果设备驱动清楚地知道自己要做什么,也可以使用同步接口(pm_runtime_get_sync/pm_runtime_put_sync等),它们会直接调用.runtime_xxx回调函数,不过,后果自负!

3.4 Runtime PM过程中的同步问题

由于.runtime_xxx回调函数可能采用异步的形式调用,以及Generic PM suspend和RPM并存的现状,要求RPM要小心处理同步问题,包括:

多个.runtime_suspend请求之间的同步;
多个.runtime_resume请求之间的同步;
多个.runtime_idle请求之间的同步;
.runtime_suspend请求和.runtime_resume请求之间的同步;
.runtime_suspend请求和system suspend之间的同步;
.runtime_resume请求和system resume之间的同步;
等等。

由此可知,RPM core将会有相当一部分代码,用来处理同步问题。

3.5 级联设备之间的Runtime PM

struct device结构中,有一个parent指针,指向该设备的父设备(没有的话为空)。父设备通常是Bus、host controller,设备的正常工作,依赖父设备。体现在RPM中,就是如下的行为:

1)parent设备下任何一个设备处于active状态,parent必须active。

2)parent设备下任何一个设备idle了,要通知parent,parent以此记录处于active状态的child设备个数。

3)parent设备下所有子设备都idle了,parent才可以idle。

以上行为RPM core会自动处理,不需要驱动工程师太过操心。

3.6 device的runtime status及其初始状态

在Runtime Power Management的过程中,device可处于四种状态:RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。

RPM_ACTIVE,设备处于正常工作的状态,表示设备的.runtime_resume回调函数执行成功;

RPM_SUSPENDED,设备处于suspend状态,表示设备.runtime_suspend回调函数执行成功;

RPM_RESUMING,设备的.runtime_resume正在被执行;

RPM_SUSPENDING,设备的.runtime_suspend正在被执行。

注4:前面说过,.runtime_idle只是suspend前的过渡,因此runtime status和idle无关。

device注册时,设备模型代码会调用pm_runtime_init接口,将设备的runtime status初始化为RPM_SUSPENDED,而kernel并不知道某个设备初始化时的真正状态,因此设备驱动需要根据实际情况,调用RPM的helper函数,将自身的status设置正确。

4. runtime PM的API汇整

RPM提供的API位于“include/linux/pm_runtime.h”中,在这里先浏览一下,目的有二:一是对前面描述的RPM运行机制有一个感性的认识;二是为后面分析RPM的运行机制做准备。

注5:我会把和驱动编写相关度较高的API加粗,其它的能不用就不用、能不看就不看!

extern int __pm_runtime_idle(struct device *dev, int rpmflags);
extern int __pm_runtime_suspend(struct device *dev, int rpmflags);
extern int __pm_runtime_resume(struct device *dev, int rpmflags);

这三个函数是RPM的idle、put/suspend、get/resume等操作的基础,根据rpmflag,有着不同的操作逻辑。后续很多API,都是基于它们三个。一般不会在设备驱动中直接使用。

extern int pm_schedule_suspend(struct device *dev, unsigned int delay);

在指定的时间后(delay,单位是ms),suspend设备。该接口为异步调用,不会更改设备的引用计数,可在driver的.rpm_idle中调用,免去driver自己再启一个timer的烦恼。

extern void pm_runtime_enable(struct device *dev);
extern void pm_runtime_disable(struct device *dev);

设备RPM功能的enable/disable,可嵌套调用,会使用一个变量(dev->power.disable_depth)记录disable的深度。只要disable_depth大于零,就意味着RPM功能不可使用,很多的API调用(如suspend/reesume/put/get等)会返回失败。

RPM初始化时,会将所有设备的disable_depth置为1,也就是disable状态,driver初始化完毕后,要根据设备的时机状态,调用这两个函数,将RPM状态设置正确。

extern void pm_runtime_allow(struct device *dev);
extern void pm_runtime_forbid(struct device *dev);

RPM core通过sysfs(drivers/base/power/sysfs.c),为每个设备提供一个“/sys/devices/.../power/control”文件,通过该文件可让用户空间程序直接访问device的RPM功能。这两个函数用来控制是否开启该功能(默认开启)。

extern int pm_runtime_barrier(struct device *dev);

这名字起的!!!

由3.3的描述可知,很多RPM请求都是异步的,这些请求会挂到一个名称为“pm_wq”的工作队列上,这个函数的目的,就是清空这个队列,另外如果有resume请求,同步等待resume完成。好复杂,希望driver永远不要用到它!!

extern int pm_generic_runtime_idle(struct device *dev);
extern int pm_generic_runtime_suspend(struct device *dev);
extern int pm_generic_runtime_resume(struct device *dev);

几个通用的函数,一般给subsystem的RPM driver使用,直接调用devie driver的相应的callback函数。

extern void pm_runtime_no_callbacks(struct device *dev);

告诉RPM core自己没有回调函数,不用再调用了(或者调用都是成功的),真啰嗦。

extern void pm_runtime_irq_safe(struct device *dev);

告诉RPM core,如下函数可以在中断上下文调用:
pm_runtime_idle()
pm_runtime_suspend()
pm_runtime_autosuspend()
pm_runtime_resume()
pm_runtime_get_sync()
pm_runtime_put_sync()
pm_runtime_put_sync_suspend()
pm_runtime_put_sync_autosuspend()

static inline int pm_runtime_idle(struct device *dev)
static inline int pm_runtime_suspend(struct device *dev)
static inline int pm_runtime_resume(struct device *dev)

直接使用同步的方式,尝试idle/suspend/resume设备,如果条件许可,就会执行相应的callback函数。driver尽量不要使用它们。

static inline int pm_request_idle(struct device *dev)
static inline int pm_request_resume(struct device *dev)

和上面类似,不过调用方式为异步。尽量不要使用它们。

static inline int pm_runtime_get(struct device *dev)
static inline int pm_runtime_put(struct device *dev)

增加/减少设备的使用计数,并判断是否为0,如果为零,尝试调用设备的idle callback,如果不为零,尝试调用设备的resume callback。

这两个接口是RPM的正统接口啊,多多使用!

static inline int pm_runtime_get_sync(struct device *dev)
static inline int pm_runtime_put_sync(struct device *dev)
static inline int pm_runtime_put_sync_suspend(struct device *dev)

和上面类似,只不过为同步调用。另外提供了一个可直接调用suspend的put接口,何必的!


static inline int pm_runtime_autosuspend(struct device *dev)
static inline int pm_request_autosuspend(struct device *dev)
static inline int pm_runtime_put_autosuspend(struct device *dev)
static inline int pm_runtime_put_sync_autosuspend(struct device *dev)

autosuspend相关接口。所谓的autosuspend,就是在suspend的基础上,增加一个timer,还是觉得有点啰嗦。不说了。

static inline void pm_runtime_use_autosuspend(struct device *dev)
static inline void pm_runtime_dont_use_autosuspend(struct device *dev)
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);

控制是否使用autosuspend功能,以及设置/获取autosuspend的超时值。

总结一下:总觉得这些API所提供的功能有些重叠,重叠的有点啰嗦。可能设计者为了提供更多的便利,可过渡的便利和自由,反而是一种束缚和烦恼!

5. runtime PM的使用步骤

觉得上面已经讲了,就不再重复了。

 

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net

标签: Linux PM 电源管理 runtime rpm

评论:

goodog
2021-03-31 10:44
@wowo:RPM和内核的devfreq子系统冲突吗,两者有交叉功能吗,还是两者是依赖关系?
mating
2019-08-23 11:14
文中“设备驱动代码可在进程和中断两种上下文执行,因此put和get等接口,要么是由用户进程调用,要么是由中断处理函数调用。由于这些接口可能会执行device的.runtime_xxx回调函数,而这些接口的执行时间是不确定的,有些可能还会睡眠等待。这对用户进程或者中断处理函数来说,是不能接受的”

rpm_suspend有可能会睡眠,而代码中,用了spin_lock,不是有问题啦?
    might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe);
1008
1009    spin_lock_irqsave(&dev->power.lock, flags);
1010    retval = rpm_suspend(dev, rpmflags);
1011    spin_unlock_irqrestore(&dev->power.lock, flags);
mating
2019-08-23 11:47
@mating:__pm_runtime_suspend用的spin_lock_irqsave(&dev->power.lock   上锁,调用rpm_suspend里面是spin_unlock(&dev->power.lock); 解锁,这个接口不对称不影响的吗?
小亮
2017-08-24 08:53
hi wowo __pm_runtime_set_status 函数中有一段代码如下:
     if (parent) {
         spin_lock_nested(&parent->power.lock, SINGLE_DEPTH_NESTING);
  
         /*
          * It is invalid to put an active child under a parent that is
          * not active, has runtime PM enabled and the
          * 'power.ignore_children' flag unset.
          */
         if (!parent->power.disable_depth
             && !parent->power.ignore_children
             && parent->power.runtime_status != RPM_ACTIVE)
             error = -EBUSY;
         else if (dev->power.runtime_status == RPM_SUSPENDED)
             atomic_inc(&parent->power.child_count);
为什么dev->power.runtime_status为RPM_SUSPENDED了,反而要增加dev父设备中的活动的child_count的个数呢,而不是减少呢?
wowo
2017-08-24 09:44
@小亮:没毛病,此时就是从RPM_SUSPENDED(runtime_status)切换到RPM_ACTIVE(status)啊。
小亮
2017-08-24 10:13
@wowo:明白了,还是代码阅读的不够认真,如果到达了if (parent) {}时,此时的status是RPM_ACTIVE.如果dev->power.runtime_status 为RPM_SUSPENDED,,就是以此状态的变化。
笨小孩
2016-11-02 17:50
蜗蜗,你好,问一下pm_runtime_last_busy_and_autosuspend(device)这个函数的功能是什么?一般在什么情况下执行呢?
wowo
2016-11-03 21:59
@笨小孩:你看的哪个版本的kernel呢?为什么我没见过这个函数?
yokingwang
2015-07-28 10:04
为作者点个赞, 文章写的很好,最近看电源管理,帮助很大!
谢谢了!
wowo
2015-07-28 12:15
@yokingwang:不用客气。
wowo
2015-06-03 20:07
@bear20081015:回复的层数有限制,不然都挤在右边不美观。
“比如在系统suspend的时候可能需要先把设备从 pm runtime suspend中resume回来,然后再放到系统suspend希望的硬件状态中去”,这意味driver需要在suspend回调中resume device,那么driver要很清楚的知道自己在做什么,因而不能调用“pm_runtime_get”因为它是异步的,是不起作用的。
driver可以这么做:
1. 调用pm_runtime_get_sync。
2. 不要使用runtime接口了,反正resume后,需要根据实际情况重置runtime状态。
因此不会存在你最早所说的问题吧?
wowo
2015-06-03 20:08
@wowo:然后还有一种方法,driver可以在prepare中get并resume设备。
bear20081015
2015-06-03 23:30
@wowo:谢谢wowo耐心的回答,这样基本上就没问题了。

总觉得PM RUNTIME设计的不太好啊,比如driver owner认为在PM_RUNTIME_DISABLE之前都可以调用PM RUNTIME的API,但实际上只有sync的PM RUNTIME API才会生效。当PM runtime和suspend,power domain搅在一起的时候,驱动工程师需要了解的限制就比较多了。我们最近就碰到了一个驱动错误使用PM RUNTIME导致的问题。

另外问点题外话,
1. 除了评论区交流外,能否考虑点其他的交流方式,比如邮件列表或者qq交流群啥的?感觉评论区这种方式效率不太高啊。
2. 这里接受别人的文章么?需要啥流程么?我平时也比较喜欢作笔记记录自己的学习过程,但没人review所以都是记录在自己的笔记本中,希望能有更多的机会和你们交流。
wowo
2015-06-04 08:50
@bear20081015:我也有同感,runtime PM设计的有点啰嗦,不优雅易用。看看最终会进化成什么样子吧。

关于评论交流的问题,我们之间也讨论过很多次,主要目的是想把大家的交流留存下来。其实很多时候,评论的帮助很大,有时会比正文还大。确实,这样效率不是很高。最好的方法是,每一条评论,都可以通过邮件、QQ、微信等方式及时通知到相关人员。可我对网页相关的东西懂得太少,没法自由的定制。

蜗窝接受其它文章的,原则是原创、有自己的思考在里面。至于其它的流程,都不需要,只要注册个账号就行了。不过后台编辑器不太好用,要写很多东西的话,可能需要Windows Live Writer等类似的PC软件。
目前主要是我和Linuxer在维护,我写power management有关的东西,Linuxer写中断、timer、同步有关的东西。由于业余时间太少,涉及到的点不多,还是希望有志同道合的人一起维护的。说不定哪天可以出本书呢。
cy
2015-10-06 11:27
@wowo:system wide的suspend过程中,在device的prepare回调,如果发现设备已经被runtime suspend了,就直接绕过这个设备的suspend流程,接着处理下一个设备。至少一些pci设备的驱动是按照上面的方式来直接跳过device suspend的。
我看不出有必要resume这个设备?
wowo
2015-10-08 09:05
@cy:还是有的:
例如某一个设备,正常工作时为S0状态,Runtime suspend时为S1状态,Suspend时为S2状态。但是,S1状态不能直接切换到S2状态,必须通过S0状态切换(如在S0状态时写寄存器)。
这时,suspend的时候,就需要runtime resume。
cy
2015-10-08 18:57
@wowo:你指的是D0,D1...etc?
wowo
2015-10-08 19:05
@cy:呵呵,那些名字是我随便说的,只是为了说明其中的意义。
cy
2015-10-08 20:24
@wowo:看到一个commit id, 如果driver认为没有必要resume,那就跳过了,
还是跟driver的实现有关,就算system wide的suspend想把device放到D3,如果runtime suspend的设备是D2,并且设备的driver告诉pm core说,我在D2也没关系,那么pm core就会让设备留在D2。

commit aae4518b3124b29f8dc81c829c704fd2df72e98b
Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Date:   Fri May 16 02:46:50 2014 +0200

    PM / sleep: Mechanism to avoid resuming runtime-suspended devices unnecessarily
蜗蜗
2015-06-03 16:31
@bear20081015:
我把您的问题贴到了这里,我们在这里讨论。
================================================================================
1.一个设备在系统suspend之前处于RPM_SUSPENDED状态;此时dev->power.usage_count=0,dev->power.runtime_state = RPM_SUSPENDED;
2.在dev->prepare函数, system suspend框架会调用pm_runtime_get_noresume,此时dev->power.usage_count=1, runtime_state仍然是RPM_SUSPENEDED;
3.接着,system suspend会调用device_suspend_late,这个函数中调用了__pm_runtime_disable()。如果恰好在此之前,有人希望通过pm_runtime_get来resume这个设备,那么就会把一个pending的resume request放到pm runtime work queue上;此时usage_count=2, runtime_state = RPM_SUSPENDED;
4. 在__pm_runtime_disable中,函数会调用__pm_runtime_barrier,这会取消step3中的pending resume请求。 但不会将usage_count减1。此时usage_count=2, runtime_state = RPM_SUSPENDED。
5. 当系统resume回来的时候,它会调用pm_runtime_put来将usage_count减1。 此时usage_count=1, runtime_state = RPM_SUSPENDED。

可以看到,经过这几个步骤后,device的runtime_state和usage_count出现了不匹配的情况,runtime_state=RPM_SUSPENDED的时候usage_count应该是0,但现在变成了1。我做的实验也是这样。不知道这算不算runtime的bug呢?还是我上面哪一步有问题?请指教,谢谢!
蜗蜗
2015-06-03 16:38
@蜗蜗:我觉得问题的关键是:
“如果恰好在此之前,有人希望通过pm_runtime_get来resume这个设备,那么就会把一个pending的resume request放到pm runtime work queue上;此时usage_count=2, runtime_state = RPM_SUSPENDED; ”
上面的步骤执行后,系统suspend就应该abort了,后续的__pm_runtime_disable就不应该接着执行。
bear20081015
2015-06-03 17:11
@蜗蜗:wowo,哪里的代码体现了这一点?我看到的代码是如果在device_suspend_late里面, __pm_runtime_disable之前有人调用了pm_runtime_abort的话,系统是不会从suspend中abort的。如果是在device_suspend的pm_runtimer_barrier之前的话则系统会abort。不知道我理解的对不对?
wowo
2015-06-03 17:29
@bear20081015:dpm_suspend_start-->dpm_suspend-->device_suspend-->__device_suspend-->pm_runtime_barrier
        直到这个地方,device的resume请求都是可被处理的。
然后在pm_runtime_barrier之后,会suspend设备。
设备suspend了,也就意味着不会再产生resume事件了,也就不应该再对其执行pm_rutime_put操作了(这是由设备的suspend代码保证的)。
因此在随后的device_suspend_late里面,已经假设设备不会再有resume请求,这也是suspend、suspend_late的设计初衷。
因此suspend_late时,不应该出现我们讨论的这种情况。
wowo
2015-06-03 17:30
@wowo:错了,应该是“也就不应该再对其执行pm_rutime_get操作了”
bear20081015
2015-06-03 17:44
@wowo:"设备suspend了,也就意味着不会再产生resume事件了,也就不应该再对其执行pm_rutime_put操作了(这是由设备的suspend代码保证的)。"如果有这个假设的话,那么是没有问题的。

还是有点疑问,既然有这样的假设,
1.为什么还要在suspend_late中调用pm_runtime_disable呢,反正这时候也不会有pm runtime的操作了吧?
2. 为啥不干脆在__device_suspend->pm_runtime_barrier之后就直接将pm_runtime_disable了呢?是让device在suspend函数中还能调用pm runtime么?

问题比较多,见谅!
wowo
2015-06-03 18:43
@bear20081015:不用客气,也不要再说“请教”之类的话了,我们是在讨论,愈辩愈明嘛。
关于suspend后device的行为,可以参考“include/linux/pm.h”中的注释(我在“http://www.wowotech.net/linux_kenrel/suspend_and_resume.html”中也有强调):
* @suspend: Executed before putting the system into a sleep state in which the
*      contents of main memory are preserved.  The exact action to perform
*      depends on the device's subsystem (PM domain, device type, class or bus
*      type), but generally the device must be quiescent after subsystem-level
*      @suspend() has returned, so that it doesn't do any I/O or DMA.
*      Subsystem-level @suspend() is executed for all devices after invoking
*      subsystem-level @prepare() for all of them.

至于你的两个疑问,其实是一个,我猜原因是这样的:
对系统的suspend而言,操作对象不是单一设备,所以suspend或者suspend_late是针对系统中的所有设备的。suspend对应pm_runtime_barrier,suspend_late对应pm_runtime_disable,有两个好处:
1. 结构上是清晰的(suspend之后调用pm_runtime_disable也有可能是可以的)。
2. 设备之间可能有依存关系,所以先barrier所有设备的runtime请求,然后再disable所有设备的runtime功能,是比较合符逻辑。

嗯,其实这两个好处也可以归结为一个,就是结构清晰,至于功能上,可能没有特别深层次的考虑(毕竟,设备已经suspend了,什么时候pm_runtime_disable,都无伤大雅了)。
bear20081015
2015-06-03 19:44
@bear20081015:为啥不能在最下面回复了???长度限制么?

wowo,不好意思,关于pm runtime在suspend中的设计还是觉得有点奇怪:
我看到pm runtime的documents说对于某些设备来说,有可能它在系统suspend和pm runtime suspend下的实现会不一样,比如在系统suspend的时候可能需要先把设备从 pm runtime suspend中resume回来,然后再放到系统suspend希望的硬件状态中去。如果是这样的话,假如下面的情形:
1. 系统suspend之前设备处于pm runtime suspend状态;
2. 在device->suspend callback function中,先调用pm_runtime_put将设备唤醒,再把它放到某些更低功耗的状态中。

这种用法是不是会有下面的问题:
1. 执行pm runtime是在workqueue中,而这个workqueue在suspend比较早的阶段就被freeze了, 也就是说pm_runtime_put并不会真正打开设备?
2. 因为pm_runtime_barrier是在device->suspend之前调用的,如果这个resume的请求被pending在wq上的话,等执行到suspend_late的pm_runtime_disable时还是会产生我最早说的问题吧?
gerrie
2015-05-08 13:37
lz 文章有高深..正在理解中
新版驱动和sysfs结合比较多.
我想问有没办法通过sysfs在用户空间主动挂起一个设备呢??
例如我的某个驱动 在sys的power接口中有
autosuspend_delay_ms
control
runtime_active_time
runtime_status
runtime_suspended_time
wakeup
wakeup_abort_count
wakeup_active
wakeup_active_count
wakeup_count
wakeup_expire_count
wakeup_last_time_ms
wakeup_max_time_ms
wakeup_prevent_sleep_time_ms
wakeup_total_time_ms
这些东西,我尝试过.
#echo disabled > control
#cat control
disabled
但是设备仍在供电.
或者 这样.
root@A0001:/sys/bus/platform/drivers/msm-dwc3 # echo -n "msm_dwc3">unbind
这样做可以.但是设备被系统resume时,系统会宕机重启..
其实原理上能实现么?通过sysfs在用户空间主动挂起一个设备
wowo
2015-05-08 15:00
@gerrie:我相信kernel不会提供这样的接口的,driver的suspend,由kernel统一调度了。如果需要特列,open、close也就行啊,或者提供一个ioctl,或者driver自行提供一个sysfs节点,等等。总之不需要这样的标准接口。
xuchu
2015-03-05 09:28
学习了,帮助挺大的,多谢楼主分享。
wowo
2015-03-05 10:44
@xuchu:不客气,感谢支持~~~
qkhhyga
2015-03-04 14:53
@wowo:我有一个问题想请教一下:在runtime pm中,在设计driver时,如果在外设处于suspend状态,突然外设有事件上报,即产生中断,在中断处理函数中去异步地get引用计数,那么此时runtime_resume有可能会比读取外设数据的执行程序要晚,这样的问题如何解决?即使resume在此之前,也会带来一定的延时,影响中断的响应时间
wowo
2015-03-04 15:43
@qkhhyga:这取决于runtime_resume和“读取外设数据的执行程序”需要做哪些事情。
首先有一点是要保证的,设备必须正确resume后,才能读取数据。那么执行流就应该这样:pm_runtime_get-->runtime_resume-->读取数据。因此我们的设计应该保证,在runtime_resume之后,才能读取数据。或者,读取数据的程序,就是runtime_resume触发执行的。因此,不会存在runtime_resume执行较晚的可能(要在设计上杜绝)。
至于所担心的影响中断响应,则完全不必,pm_runtime_get是异步的,因此会在中断结束后,起一个线程执行runtime_resume,不会影响中断。
qkhhyga
2015-03-05 10:24
@wowo:@wowo:非常感谢你的解答,另外,如果在外设suspend的时候,来了一个中断,而这个中断是要求cpu读取外设数据,那么,因为pm_runtime_get是异步的,所以resume是在workqueue中执行(需要调度),而读取外设数据又要保证在resume之后再读,这样的话就会影响应用程序的响应时间了。这是否也是在设计上应该注意的呢?比如把异步的改为同步的get
wowo
2015-03-05 10:38
@qkhhyga:是的,这就是PM的代价,也是PM QOS framework的目的所在。如果应用程序对这个延迟很在意,那么,系统设计者就要想办法,以其它代价去减小这个延迟。
所以,首先要确定的是:应用的延迟容忍是多少(request),driver的延迟是多少(constraint)?
最后,用同步get不是好方法,这样的话这个应用是爽了,可其它模块就悲剧了(中断延迟过大,同时需要保证你的runtime_resume可以在中断上下文执行)。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog