赞
踩
链接:
https://blog.csdn.net/u013686019/article/details/53907324
http://www.wowotech.net/pm_subsystem/wakeup_events_framework.html
https://blog.csdn.net/longwang155069/article/details/52980348
https://www.jianshu.com/p/92591a82486a
本文内容节选自上述文章,本文仅作为备忘使用.
1:系统休眠Sleep,Linux
Kernel中称作Suspend.系统进入Suspend状态确切来说时CPU进入了Suspend模式,因为对整个系统来说CPU休眠是整个系统休眠的先决条件.
CPU Suspend即CPU进入Wait for interrupt状态(WFI),SW完全不跑了,停在suspend
workqueue里面.
Android系统从灭屏到系统进入Suspend的大体流程框架如下:
4:wakeup events framework architecture
下面图片描述了wakeup events framework的architecture:
1):wakeup events framework
core,在drivers/base/power/wakeup.c中实现,提供了wakeup events
framework的核心功能,包括:
抽象wakeup source和wakeup event的概念
向各个device driver提供wakeup source的注册,使能等接口
向各个device driver提供wakeup event的上报,停止等接口
向上层的PM core(包括wakeup count,auto
sleep,suspend,hibernate等模块)提供wakeup
event的查询接口,以判断是否可以suspend,是否需要终止正在进行的suspend
2):wakeup events framework
sysfs,将设备的wakeup信息,以sysfs的形式提供到用户空间,供用户空间程序查询,配置.在drivers/base/power/sysfs.c中实现.
3):wake lock/unlock,为了兼容Android旧的wakeup
lock机制而留下的一个后门,扩展wakeup events framework的功能,允许用户空间程序报告/停止wakeup
events.
换句话说,该后门允许用户空间的任一程序决定系统是否可以休眠.
4):wakeup count,基于wakeup events framework,解决用户空间同步的问题.
5):auto sleep,允许系统在没有活动时(即一段时间内,没有产生wakeup event),自动休眠.
5:源码分析
5.1:wakeup source和wakeup event:
struct device {
...
struct dev_pm_info power;
...
};
struct dev_pm_info {
...
unsigned int can_wakeup:1;
...
#ifdef CONFIG_PM_SLEEP
...
struct wakeup_source *wakeup;
...
#else
unsigned int should_wakeup:1;
#endif
};
can_wakeup
,标识本设备是否具有唤醒能力,只有具备唤醒能力的设备,才会在sysfs中有一个power目录.用于提供所有的wakeup信息,这些信息是以struct
wakeup_source 的形式组织起来的,也就是上面wakeup指针.
struct wakeup_source {
const char *name;
struct list_head entry;
spinlock_t lock;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1;
bool autosleep_enabled:1;
};
一个wakeup source代表了一个具有唤醒能力的设备,也称该设备为一个wakeup
source.该结构中各个字段的意义如下:
name,该wakeup source的名称,一般为对应的device
name(有个例外,就是wakelock)
entery,用于将所有的wakeup source挂在一个链表中
timer,timer_expires,一个wakeup source产生了wakeup event,称作wakeup
source activate,wakeup
event处理完毕后(不再需要系统为此保持active),称作deactivate.
activate和deactivate的操作可以由driver亲自设置,也可以在activate时,指定一个timeout时间,时间到达后,由wakeup
events framework自动将其设置为deactivate状态.
这里的timer以及expires时间,就是用来实现该功能.
total_time,该wakeup source处于activate状态的总时间(可以指示该wakeup
source对应的设备的繁忙程度,耗电等级).
max_time,该wakeup source持续处于activate状态的最大时间(越长越不合理).
last_time,该wakeup source上次active的开始时间.
start_prevent_time,该wakeup source开始阻止系统自动睡眠(auto
sleep)的时间点.
prevent_sleep_time,该wakeup source阻止系统自动睡眠的总时间.
event_count,wakeup source上报的event个数.
active_count,wakeup source activate的次数.
relax_count, wakeup source deactivate的次数.
expire_count,wakeup source timeout到达的次数.
wakeup_count,wakeup source终止suspend过程的次数.
active,wakeup source的activate状态.
autosleep_enabled,记录系统auto sleep的使能状态(每个wakeup
source都重复记录这样一个状态,这种设计真实不敢恭维!).
wakeup source代表一个具有唤醒能力的设备,该设备产生的可以唤醒系统的事件,就称作wakeup
event.
当wakeup source产生wakeup event时,需要将wakeup
source切换为activate状态,当wakeup event处理完毕后,要切换为deactivate状态.
因此,我们再来理解一下几个wakeup
source比较混淆的变量:event_count,active_count和wakeup_count:
event_count,wakeup source产生的wakeup event的个数
active_count,产生wakeup event时,wakeup
source需要切换到activate状态.但并不是每次都要切换,因为有可能已经处于activate状态了,因此active_count可能小于event_count.
换句话说,很有可能在前一个wakeup event没被处理完时,又产生了一个,这从一定程度上反映了wakeup
source所代表的设备的繁忙程度.
wakeup_count,wakeup source在suspend过程中产生wakeup
event的话,就会终止suspend过程,该变量记录了wakeup
source终止suspend过程的次数(如果发现系统总是suspend失败,检查一下各个wakeup
source的该变量,就可以知道问题出在谁身上了).
5.2:几个counters:
在drivers\base\power\wakeup.c中,有几个比较重要的计数器,是wakeup events
framework的实现基础,包括:
1)registered wakeup events和saved_count:
记录了系统运行以来产生的所有wakeup event的个数,在wakeup source上报event时加1.
这个counter对解决用户空间同步问题很有帮助,因为一般情况下(无论是用户程序主动suspend,还是auto
sleep),由专门的进程(或线程)触发suspend.
当这个进程判断系统满足suspend条件,决定suspend时,会记录一个counter值(saved_count),在后面suspend的过程中,如果系统发现counter有变,则说明系统产生了新的wakeup
event,这样就可以终止suspend.
2)wakeup events in progress
记录正在处理的event个数.
当wakeup source产生wakeup event时,会通过wakeup events
framework提供的接口将wakeup
source设置为activate状态.当该event处理结束后,设置为deactivate状态.activate到deactivate的区间,表示该event正在被处理.
当系统中有任何正在被处理的wakeup
event时,则不允许suspend.如果suspend正在进行,则要终止.
思考一个问题:registered wakeup events在什么时候增加?答案是在wakeup events in
progress减小时,因为已经完整的处理完一个event了,可以记录在案了.
基于这种特性,kernel将它俩合并成一个32位的整型数,以原子操作的形式,一起更新.这种设计巧妙的让人叫绝,值得我们学习.具体如下:
(registered wakeup events|wakeup events in progress)
static atomic_t combined_event_count = ATOMIC_INIT(0);
#define IN_PROGRESS_BITS (sizeof(int) * 4)
#define MAX_IN_PROGRESS ((1
<< IN_PROGRESS_BITS) - 1)
static void split_counters(unsigned int *cnt, unsigned int
*inpr)
{
unsigned int comb =
atomic_read(&combined_event_count);
*cnt = (comb >> IN_PROGRESS_BITS);
*inpr = comb & MAX_IN_PROGRESS;
}
定义和读取.
cec = atomic_add_return(MAX_IN_PROGRESS,
&combined_event_count);
wakeup events in progress减1,registered wakeup events加1.
cec = atomic_inc_return(&combined_event_count);
wakeup events in progress加1.
5.3:wakeup events framework的核心功能:
wakeup events framework的核心功能体现在它向底层的设备驱动所提供的用于上报wakeup
event的接口,这些接口根据操作对象可分为两类:
类型一(操作对象为wakeup source,编写设备驱动时,一般不会直接使用):
extern void __pm_stay_awake(struct wakeup_source *ws);
extern void __pm_relax(struct wakeup_source *ws);
extern void __pm_wakeup_event(struct wakeup_source *ws,
unsigned int msec);
__pm_stay_awake,通知PM core,ws产生了wakeup
event,且正在处理,因此不允许系统suspend(stay awake)
__pm_relax,通知PM core,ws没有正在处理的wakeup
event,允许系统suspend(relax)
__pm_wakeup_event,为上边两个接口的功能组合,通知PM core,ws产生了wakeup
event,会在msec毫秒内处理结束(wakeup events framework自动relax)
注:
__pm_stay_awake和__pm_relax应成对调用
上面3个接口,均可以在中断上下文调用
类型二(操作对象为device,为设备驱动的常用接口):
extern int device_wakeup_enable(struct device *dev);
extern int device_wakeup_disable(struct device *dev);
extern void device_set_wakeup_capable(struct device *dev, bool
capable);
extern int device_init_wakeup(struct device *dev, bool
val);
extern int device_set_wakeup_enable(struct device *dev, bool
enable);
extern void pm_stay_awake(struct device *dev);
extern void pm_relax(struct device *dev);
extern void pm_wakeup_event(struct device *dev, unsigned int
msec);
device_set_wakeup_capable,设置dev的can_wakeup标志(enable或disable,可参考5.1小节),并增加或移除该设备在sysfs相关的power文件.
device_wakeup_enable/device_wakeup_disable/device_set_wakeup_enable,对于can_wakeup的设备,使能或者禁止wakeup功能,主要是对struct
wakeup_source 结构的相关操作.
device_init_wakeup,设置dev的can_wakeup标志.若是enable,同时调用
device_wakeup_enable 使能wakeup功能.
pm_stay_awake,pm_relax,pm_wakeup_event,直接调用上面的wakeup
source操作接口,操作device的struct wakeup_source变量,处理wakeup events.
5.3.1:device_set_wakeup_capable
该接口位于在drivers/base/power/wakeup.c中,代码如下:
void device_set_wakeup_capable(struct device *dev, bool
capable)
{
if (!!dev->power.can_wakeup ==
!!capable)
return;
if (device_is_registered(dev) &&
!list_empty(&dev->power.entry)) {
if (capable) {
if
(wakeup_sysfs_add(dev))
return;
} else {
wakeup_sysfs_remove(dev);
}
}
dev->power.can_wakeup = capable;
}
该接口的实现很简单,主要包括sysfs的add/remove和can_wakeup标志的设置两部分.如果设置can_wakeup标志,则调用wakeup_sysfs_add,向该设备的sysfs目录下添加power文件夹,
并注册相应的attribute文件.如果清除can_wakeup标志,执行sysfs的移除操作.
wakeup_sysfs_add/wakeup_sysfs_remove
位于drivers/base/power/sysfs.c中,对wakeup events
framework来说,主要包括如下的attribute文件:
static struct attribute *wakeup_attrs[] = {
#ifdef CONFIG_PM_SLEEP
&dev_attr_wakeup.attr,
&dev_attr_wakeup_count.attr,
&dev_attr_wakeup_active_count.attr,
&dev_attr_wakeup_abort_count.attr,
&dev_attr_wakeup_expire_count.attr,
&dev_attr_wakeup_active.attr,
&dev_attr_wakeup_total_time_ms.attr,
&dev_attr_wakeup_max_time_ms.attr,
&dev_attr_wakeup_last_time_ms.attr,
#ifdef CONFIG_PM_AUTOSLEEP
&dev_attr_wakeup_prevent_sleep_time_ms.attr,
#endif
#endif
NULL,
};
static struct attribute_group pm_wakeup_attr_group = {
.name =
power_group_name,
.attrs = wakeup_attrs,
};
1)wakeup
读,获得设备wakeup功能的使能状态,返回"enabled"或"disabled"字符串.
写,更改设备wakeup功能的使能状态,根据写入的字符串("enabled"或"disabled"),调用device_set_wakeup_enable接口完成实际的状态切换.
设备wakeup功能是否使能,取决于设备的can_wakeup标志,以及设备是否注册有相应的struct
wakeup_source指针,即can wakeup和may wakeup,如下:
static inline bool device_can_wakeup(struct device *dev)
{
return dev->power.can_wakeup;
}
static inline bool device_may_wakeup(struct device *dev)
{
return dev->power.can_wakeup &&
!!dev->power.wakeup;
}
2)wakeup_count
只读,获取dev->power.wakeup->event_count值.有关event_count的意义,请参考5.1小节,下同.顺便抱怨一下,
这个attribute文件的命名简直糟糕透顶了!直接用event_count就是了,用什么wakeup_count,会和wakeup
source中的同名字段搞混淆的.
3)wakeup_active_count,只读,获取dev->power.wakeup->active_count值.
4)wakeup_abort_count,只读,获取dev->power.wakeup->wakeup_count值.
5)wakeup_expire_count,只读,获dev->power.wakeup->expire_count取值.
6)wakeup_active,只读,获取dev->power.wakeup->active值.
7)wakeup_total_time_ms,只读,获取dev->power.wakeup->total_time值,单位为ms.
8)wakeup_max_time_ms,只读,获dev->power.wakeup->max_time取值,单位为ms.
9)wakeup_last_time_ms,只读,获dev->power.wakeup->last_time取值,单位为ms.
10)wakeup_prevent_sleep_time_ms,只读,获取dev->power.wakeup->prevent_sleep_time值,单位为ms.
5.3.2:device_wakeup_enable/device_wakeup_disable/device_set_wakeup_enable
以device_wakeup_enable为例:
int device_wakeup_enable(struct device *dev)
{
struct wakeup_source *ws;
int ret;
if (!dev || !dev->power.can_wakeup)
return -EINVAL;
ws =
wakeup_source_register(dev_name(dev));
if (!ws)
return -ENOMEM;
ret = device_wakeup_attach(dev, ws);
if (ret)
wakeup_source_unregister(ws);
return ret;
}
a)若设备指针为空,或者设备不具备wakeup能力,免谈,报错退出
b)调用wakeup_source_register接口,以设备名为参数,创建并注册一个wakeup
source
c)调用device_wakeup_attach接口,将新建的wakeup
source保存在dev->power.wakeup指针中
wakeup_source_register接口的实现也比较简单,会先后调用wakeup_source_create,wakeup_source_prepare,wakeup_source_add等接口,
所做的工作包括分配struct wakeup_source变量所需的内存空间,初始化内部变量,将新建的wakeup
source添加到名称为wakeup_sources的全局链表中,等等.
device_wakeup_attach接口更为直观,不过有一点我们要关注,如果设备的dev->power.wakeup非空,也就是说之前已经有一个wakeup
source了,是不允许再enable了的,会报错返回.
5.3.3:pm_stay_awake
当设备有wakeup event正在处理时,需要调用该接口通知PM core,该接口的实现如下:
void pm_stay_awake(struct device *dev)
{
unsigned long flags;
if (!dev)
return;
spin_lock_irqsave(&dev->power.lock,
flags);
__pm_stay_awake(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock,
flags);
}
void __pm_stay_awake(struct wakeup_source *ws)
{
unsigned long flags;
if (!ws)
return;
spin_lock_irqsave(&ws->lock,
flags);
wakeup_source_report_event(ws);
del_timer(&ws->timer);
ws->timer_expires = 0;
spin_unlock_irqrestore(&ws->lock,
flags);
}
由于pm_stay_awake报告的event需要经过pm_relax主动停止,因此就不再需要timer,所以__pm_stay_awake实现是直接调用wakeup_source_report_event,然后停止timer.接着看代码:
static void wakeup_source_report_event(struct wakeup_source
*ws)
{
ws->event_count++;
if (events_check_enabled)
ws->wakeup_count++;
if (!ws->active)
wakeup_source_activate(ws);
}
a)增加wakeup source的event_count,表示该source又产生了一个event
b)根据events_check_enabled变量的状态,决定是否增加wakeup_count.这和wakeup
count的功能有关,到时再详细描述
c)如果wakeup source没有active,则调用
wakeup_source_activate,activate之.这也是5.1小节所描述的,event_count和active_count的区别所在.wakeup_source_activate
的代码如下:
static void wakeup_source_activate(struct wakeup_source
*ws)
{
unsigned int cec;
freeze_wake();
ws->active = true;
ws->active_count++;
ws->last_time = ktime_get();
if (ws->autosleep_enabled)
ws->start_prevent_time =
ws->last_time;
cec =
atomic_inc_return(&combined_event_count);
trace_wakeup_source_activate(ws->name,
cec);
}
a)调用freeze_wake,将系统从suspend to
freeze状态下唤醒.有关freeze功能,请参考相关的文章.
b)设置active标志,增加active_count,更新last_time.
c)如果使能了autosleep,更新start_prevent_time,因为此刻该wakeup
source会开始阻止系统auto sleep.具体可参考auto sleep的功能描述.
d)增加“wakeup events in progress”计数(5.2小节有描述).该操作是wakeup events
framework的灵魂,增加该计数,意味着系统正在处理的wakeup event数目不为零,则系统不能suspend.
到此,pm_stay_awake执行结束,意味着系统至少正在处理一个wakeup
event,因此不能suspend.那处理完成后呢?driver要调用pm_relax通知PM core.
5.3.4:pm_relax
pm_relax和pm_stay_awake成对出现,用于在event处理结束后通知PM core,其实现如下:
void pm_relax(struct device *dev)
{
unsigned long flags;
if (!dev)
return;
spin_lock_irqsave(&dev->power.lock,
flags);
__pm_relax(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock,
flags);
}
直接调用__pm_relax,如下:
void __pm_relax(struct wakeup_source *ws)
{
unsigned long flags;
if (!ws)
return;
spin_lock_irqsave(&ws->lock,
flags);
if (ws->active)
wakeup_source_deactivate(ws);
spin_unlock_irqrestore(&ws->lock,
flags);
}
如果该wakeup
source处于active状态,调用wakeup_source_deactivate接口,deactivate之.deactivate接口和activate接口一样,是wakeup
events framework的核心逻辑,如下:
static void wakeup_source_deactivate(struct wakeup_source
*ws)
{
unsigned int cnt, inpr, cec;
ktime_t duration;
ktime_t now;
ws->relax_count++;
if (ws->relax_count != ws->active_count)
{
ws->relax_count--;
return;
}
ws->active = false;
now = ktime_get();
duration = ktime_sub(now,
ws->last_time);
ws->total_time = ktime_add(ws->total_time,
duration);
if (ktime_to_ns(duration) >
ktime_to_ns(ws->max_time))
ws->max_time =
duration;
ws->last_time = now;
del_timer(&ws->timer);
ws->timer_expires = 0;
if (ws->autosleep_enabled)
update_prevent_sleep_time(ws,
now);
cec = atomic_add_return(MAX_IN_PROGRESS,
&combined_event_count);
trace_wakeup_source_deactivate(ws->name,
cec);
split_counters(&cnt, &inpr);
if (!inpr &&
waitqueue_active(&wakeup_count_wait_queue))
wake_up(&wakeup_count_wait_queue);
}
a)relax_count加1(如果relax_count和active_count不等,则说明有重复调用,要退出).
b)清除active标记.
c)更新total_time,max_time,last_time等变量.
d)如果使能auto sleep,更新相关的变量(后面再详细描述).
e)再欣赏一下艺术,wakeup events in progress减1,registered wakeup
events加1.
f)wakeup count相关的处理,后面再详细说明.
5.3.5:pm_wakeup_event
pm_wakeup_event是pm_stay_awake和pm_relax的组合版,在上报event时,指定一个timeout时间,timeout后,自动relax,一般用于不知道何时能处理完成的场景.该接口比较简单,就不一一描述了.
5.3.6:pm_wakeup_pending
drivers产生的wakeup events,最终要上报到PM core,PM
core会根据这些events,决定是否要终止suspend过程.这表现在suspend过程中频繁调用pm_wakeup_pending接口上,
该接口的实现如下:
bool pm_wakeup_pending(void)
{
unsigned long flags;
bool ret = false;
spin_lock_irqsave(&events_lock,
flags);
if (events_check_enabled) {
unsigned int cnt, inpr;
split_counters(&cnt,
&inpr);
ret = (cnt != saved_count ||
inpr > 0);
events_check_enabled =
!ret;
}
spin_unlock_irqrestore(&events_lock,
flags);
if (ret)
print_active_wakeup_sources();
return ret;
}
该接口的逻辑比较直观,先抛开wakeup
count的逻辑不谈(后面会重点说明),只要正在处理的events不为0,就返回true,调用者就会终止suspend.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。