linux事件通知模式,linux唤醒事件框架

链接:

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的大体流程框架如下:

a4c26d1e5885305701be709a3d33442f.png

4:wakeup events framework architecture

下面图片描述了wakeup events framework的architecture:

a4c26d1e5885305701be709a3d33442f.png

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值