目录
Linux wakeup source实现
1 模块功能介绍
在Linux kernel中,wakeup source是睡眠流程中各个组件关于本业务是否同意睡眠的一套投票机制。整套机制框架基本上是围绕着combined_event_count这个变量在处理逻辑,在此变量中,高16位记录系统处理过的所有的wakeup event总数,低16位记录在处理中的wakeup events总数。在每次持锁时,处理中的wakeup events会加1(低16位);每次释放锁时,处理中的wakeup events会减1(低16位),同步已处理的wakeup event会加1(高16位)。对于每次系统是否能够进入睡眠,通过判定是否有正在处理中的wakeup events来判断。实现的主要功能有:
1)持锁功能
2)释放锁功能
3)注册锁功能
4)去注册锁功能
5)查询激活状态锁个数功能
2 功能属性
特性功能受宏CONFIG_PM_SLEEP控制,需要打开该特性的话,CONFIG_PM_SLEEP必须设置为y。
相关实现在drivers\base\power\wakeup.c文件中,相关函数声明在include\linux\pm_wakeup.h中
3 主要结构体/变量介绍
3.1 struct wakeup_source
1)结构体原型:
-
struct
wakeup_source {
-
const
char *name;
-
int id;
-
struct
list_head entry;
-
spinlock_t lock;
-
struct
wake_irq *wakeirq;
-
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;
-
struct
device *dev;
-
bool active:
1;
-
bool autosleep_enabled:
1;
-
};
2)成员变量说明:
name:顾名思义,即该wakesource的名字,方便记录查看
id:wakesource模块给本wakesource分配的ID
entry:链表结构,用于把本wakesource节点维护到系统wakesource的全局链表中
lock:保护本结构体变量访问所使用的互斥锁
wakeirq:与本wakesource绑定的唤醒中断相关结构体,用户可以把指定中断与wakesource做绑定
timer:超时使用,比如定义本wakesource为超时锁,即过一段指定的时间后把锁释放,可以使用本变量来设置
timer_expires:要设置的定时器的超时时间
total_time:记录本wakesource激活的总时长
max_time:在wakesource激活的历史中,最长一次的激活时间
last_time:最近一次访问本wakesource的时间
prevent_sleep_time:因为本wakesource的原因导致的阻止autosleep进入睡眠的总时间
event_count:如果是本wakesource被持锁,则event_count就会加1作为维测记录
active_count:注意持锁接口是可在上次没有释放锁时再次调用的,每次调用持锁接口event_count会加1,但是active_count只会在第一次active时加1
relax_count:每次释放锁时,该值会累加1,与active_count一一对应
expire_count:对应的超时锁超时的次数
wakeup_count:由于竞态的存在,这个值只是一个大概值,仅供参考,伴随event一起增加,比较鸡肋
dev:与wakesource绑定的dev设备
active:标记是否处于active激活状态
autosleep_enabled:标记autosleep是否使能
3.2 combined_event_count
static atomic_t combined_event_count = ATOMIC_INIT(0);
该变量是个组合计数变量,高16位记录wakeup event的总计数,低16位记录正在处理中的wakeup event。系统根据正在处理中的wakeup event来判断是否可以进入睡眠。
3.3 wakeup_sources
static LIST_HEAD(wakeup_sources);
所有注册的wakeup source全部维护在该链表中,便于系统进行wakesource的维护工作。
4 主要函数介绍
本节主要介绍下wakeup.c中的关键函数,因为很多内部使用的函数本身也比较简单,所以也不用再做过多介绍,感兴趣的可以参考源码实现。
在wakeup.c中,对外接口通常是成对出现的,比如
1)wakeup_source_register和wakeup_source_unregister,分别对应注册和去注册一个wakesource。
2)pm_stay_awake与pm_relax,针对device类型的对象提供的持锁和释放锁接口。
3)__pm_stay_awake和__pm_relax,则是针对wakeup_source类型的对象提供的持锁和释放锁接口。
其中2)和3)两组接口,在对应场景中配套使用即可。
4.1 wakeup_source_register
1)函数说明
函数原型 | |
struct wakeup_source *wakeup_source_register(struct device *dev, const char *name) | |
描述 | |
1、创建dev设备中的wakeup source 2、把创建的wakeup source添加到全局链表wakeup_sources中方便后续维护 | |
参数 | |
struct device *dev | 要创建wakeup source的设备 |
const char *name | 要创建的wakeup source的名字 |
返回值 | |
struct wakeup_source指针 | 创建成功的wakeup_source |
NULL | 创建失败 |
2)函数实现
-
struct wakeup_source *wakeup_source_register(
struct device *dev,
-
const
char *name)
-
{
-
struct wakeup_source *ws;
-
int ret;
-
ws = wakeup_source_create(name);
-
if (ws) {
-
if (!dev || device_is_registered(dev)) {
-
ret = wakeup_source_sysfs_add(dev, ws);
-
if (ret) {
-
wakeup_source_free(ws);
-
return
NULL;
-
}
-
}
-
wakeup_source_add(ws);
-
}
-
return ws;
-
}
实现中,先调用wakeup_source_create进行目标wakeup source的创建
如果创建失败,则返回NULL。
创建成功的话,调用wakeup_source_add添加到全局链表wakeup_sources中。
4.2 wakeup_source_unregister
1)函数说明
函数原型 | |
void wakeup_source_unregister(struct wakeup_source *ws) | |
描述 | |
删除注册的wakeup source并释放其占用的系统资源 | |
参数 | |
struct wakeup_source *ws | 需要去注册的wakeup source |
返回值 | |
无 |
2)函数实现
-
void
wakeup_source_unregister(
struct wakeup_source *ws)
-
{
-
if (ws) {
-
wakeup_source_remove(ws);
-
if (ws->dev)
-
wakeup_source_sysfs_remove(ws);
-
wakeup_source_destroy(ws);
-
}
-
}
在入参有效的情况下:
调用内部接口wakeup_source_remove 把wakeup source从全局链表中摘除
调用wakeup_source_destroy把wakeup source占用的系统资源释放。
4.3 pm_stay_awake
1)函数说明
函数原型 | |
void pm_stay_awake(struct device *dev) | |
描述 | |
上锁dev对应的wakeup source,阻止系统睡眠 | |
参数 | |
struct device *dev | 需要持锁的设备 |
返回值 | |
无 |
2)函数实现
-
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);
-
}
实现中大家可以发现,该接口还是通过调用__pm_stay_awake来操作到了dev对应的wakeup source上了;其实调用该接口的地方,也可以直接调用__pm_stay_awake(dev->power.wakeup)来达到对应的目的。
4.3 __pm_stay_awake
1)函数说明
函数原型 | |
void __pm_stay_awake(struct wakeup_source *ws) | |
描述 | |
上锁ws来达到阻止系统睡眠的目的。 | |
参数 | |
struct wakeup_source *ws | 需要上锁的wakeup source |
返回值 | |
无 |
2)函数实现
-
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,
false);
-
del_timer(&ws->timer);
-
ws->timer_expires =
0;
-
spin_unlock_irqrestore(&ws->
lock, flags);
-
}
在接口wakeup_source_report_event中,会对组合变量combined_event_count的低16位做加1动作,从而达到阻止睡眠的目的,因为睡眠流程就是 判断该变量的低16位是否为0来决策是否满足睡眠条件的。
把对应的timer删掉,因为此wakeup source并不是延迟锁,不需要timer
4.4 pm_relax
1)函数说明
函数原型 | |
void pm_relax(struct device *dev) | |
描述 | |
与pm_stay_awake对应,在对应业务处理完成后,把持有的睡眠锁释放掉。 | |
参数 | |
struct device *dev | 需要释放锁的dev设备 |
返回值 | |
无 |
2)函数实现
-
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来达到释放其睡眠锁的目的,释放后,系统将不会再因为本wakeup source而阻止睡眠。
4.5 __pm_relax
1)函数说明
函数原型 | |
void __pm_relax(struct wakeup_source *ws) | |
描述 | |
与__pm_stay_awake对应,在对应业务处理完成后,把持有的睡眠锁释放掉。 | |
参数 | |
struct wakeup_source *ws | 需要释放锁的wakeup source |
返回值 | |
无 |
2)函数实现
-
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_deactivate来达到释放其睡眠锁的目的,释放后,系统将不会再因为本wakeup source而阻止睡眠。主要通过对combined_event_count的低16位进行减1来达到释放锁的效果。在释放锁后,如果combined_event_count的低16位为0,则表示当前已经没有在处理中的wakeup source了,该接口会触发wakeup_count_wait_queue等待队列运行,工作队列检查满足睡眠条件就会继续走睡眠流程,该功能wake source机制适通过pm_get_wakeup_count接口与autosleep配合使用的。
4.6 pm_get_wakeup_count
1)函数说明
函数原型 | |
bool pm_get_wakeup_count(unsigned int *count, bool block) | |
描述 | |
获取wakeup event值(combined_event_count高16位)与正在处理的wakeup event的是否为0(combined_event_count低16位)。 | |
参数 | |
unsigned int *count | 出参,combined_event_count高16位,历史上wakeup event的总数 |
bool block | 入参,是否要等combined_event_count低16位为0才返回 |
返回值 | |
Bool | combined_event_count低16位是否为0 false:低16位不为0 true:低16位为0 |
2)函数实现
-
bool pm_get_wakeup_count(unsigned int *count, bool block)
-
{
-
unsigned
int cnt, inpr;
-
if (block) {
-
DEFINE_WAIT(wait);
-
for (;;) {
-
prepare_to_wait(&wakeup_count_wait_queue, &wait,
-
TASK_INTERRUPTIBLE);
-
split_counters(&cnt, &inpr);
-
if (inpr ==
0 ||
signal_pending(current))
-
break;
-
pm_print_active_wakeup_sources();
-
schedule();
-
}
-
finish_wait(&wakeup_count_wait_queue, &wait);
-
}
-
split_counters(&cnt, &inpr);
-
*count = cnt;
-
return !inpr;
-
}
如果入参block为0,则仅仅对入参count赋值当前wakeup event历史处理的总数,并返回当前combined_event_count低16位是否为0
如果入参block为1,则需要一直等到combined_event_count低16位为0或者当前挂起进程有事件需要处理时才退出,退出时的操作和1)中一样,一是对入参count赋值当前wakeup event历史处理的总数,二是返回当前combined_event_count低16位是否为0。该分支的等待队列为__pm_relax检查满足睡眠条件时触发调度运行。
4.7 pm_wakeup_pending
1)函数说明
函数原型 | |
bool pm_wakeup_pending(void) | |
描述 | |
确定当前是否满足睡眠条件。 | |
参数 | |
无 | |
返回值 | |
Bool | 是否可以睡眠 True:可以睡眠 False:不可以睡眠 |
2)函数实现
-
bool pm_wakeup_pending(void)
-
{
-
unsigned
long flags;
-
bool ret =
false;
-
raw_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;
-
}
-
raw_spin_unlock_irqrestore(&events_lock, flags);
-
if (ret) {
-
pm_pr_dbg(
"Wakeup pending, aborting suspend\n");
-
pm_print_active_wakeup_sources();
-
}
-
return ret ||
atomic_read(&pm_abort_suspend) >
0;
-
}
返回值有2个参考点:
combined_event_count低16位是否为0,即正在活动状态的wakeup source
pm_abort_suspend值是否>0,如果大于0,表示睡眠流程中出现了唤醒相关的中断或事件,唤醒事件通过调用pm_system_wakeup接口来给pm_abort_suspend值做加1操作。
Wakeup.c中的函数还有不少配套的接口,但是核心的接口应该是就这几个,只要把这几个核心的接口吃透,那么可以认为就是掌握了这个模块的功能和运行机制,对我们后续构建自己的wakeup机制也会有助力。
5 函数工作时序
前提条件:
CONFIG_PM_SLEEP特性开关打开,使能wakeup功能。
工作步骤:
1)dev设备或者其他需要上锁的模块,调用wakeup_source_register来注册对应的wakeup source
2)在处理业务过程中,为了防止业务没处理完系统就睡下去,那么dev设备需要通过调用pm_stay_awake或者__pm_stay_awake来投票阻止睡眠。
3)当dev处理完自己的业务后,通过调用pm_relax或者__pm_relax来投票允许睡眠
4)__pm_relax在释放票的过程中,检查到当前没有正在处理的持票事件时,会触发wakeup_count_wait_queue运行。
5)然后wakeup_count_wait_queue所在的接口pm_get_wakeup_count会返回到autosleep的工作队列中继续走睡眠流程。