linux电源管理--wakesource

目录

Linux wakeup source实现

2 功能属性

3 主要结构体/变量介绍

3.1 struct wakeup_source

3.2 combined_event_count

3.3 wakeup_sources

4 主要函数介绍

4.1 wakeup_source_register

4.2 wakeup_source_unregister

4.3 pm_stay_awake

4.3 __pm_stay_awake

4.4 pm_relax

4.5 __pm_relax

4.6 pm_get_wakeup_count

4.7 pm_wakeup_pending

5 函数工作时序


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)结构体原型:


   
   
  1. struct wakeup_source {
  2. const char *name;
  3. int id;
  4. struct list_head entry;
  5. spinlock_t lock;
  6. struct wake_irq *wakeirq;
  7. struct timer_list timer;
  8. unsigned long timer_expires;
  9. ktime_t total_time;
  10. ktime_t max_time;
  11. ktime_t last_time;
  12. ktime_t start_prevent_time;
  13. ktime_t prevent_sleep_time;
  14. unsigned long event_count;
  15. unsigned long active_count;
  16. unsigned long relax_count;
  17. unsigned long expire_count;
  18. unsigned long wakeup_count;
  19. struct device *dev;
  20. bool active: 1;
  21. bool autosleep_enabled: 1;
  22. };

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)函数实现


   
   
  1. struct wakeup_source *wakeup_source_register( struct device *dev,
  2. const char *name)
  3. {
  4. struct wakeup_source *ws;
  5. int ret;
  6. ws = wakeup_source_create(name);
  7. if (ws) {
  8. if (!dev || device_is_registered(dev)) {
  9. ret = wakeup_source_sysfs_add(dev, ws);
  10. if (ret) {
  11. wakeup_source_free(ws);
  12. return NULL;
  13. }
  14. }
  15. wakeup_source_add(ws);
  16. }
  17. return ws;
  18. }

实现中,先调用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)函数实现


   
   
  1. void wakeup_source_unregister( struct wakeup_source *ws)
  2. {
  3. if (ws) {
  4. wakeup_source_remove(ws);
  5. if (ws->dev)
  6. wakeup_source_sysfs_remove(ws);
  7. wakeup_source_destroy(ws);
  8. }
  9. }

在入参有效的情况下:

调用内部接口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)函数实现


   
   
  1. void pm_stay_awake(struct device *dev)
  2. {
  3. unsigned long flags;
  4. if (!dev)
  5. return;
  6. spin_lock_irqsave(&dev->power. lock, flags);
  7. __pm_stay_awake(dev->power.wakeup);
  8. spin_unlock_irqrestore(&dev->power. lock, flags);
  9. }

实现中大家可以发现,该接口还是通过调用__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)函数实现


   
   
  1. void __pm_stay_awake( struct wakeup_source *ws)
  2. {
  3. unsigned long flags;
  4. if (!ws)
  5. return;
  6. spin_lock_irqsave(&ws-> lock, flags);
  7. wakeup_source_report_event(ws, false);
  8. del_timer(&ws->timer);
  9. ws->timer_expires = 0;
  10. spin_unlock_irqrestore(&ws-> lock, flags);
  11. }

在接口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)函数实现


   
   
  1. void pm_relax(struct device *dev)
  2. {
  3. unsigned long flags;
  4. if (!dev)
  5. return;
  6. spin_lock_irqsave(&dev->power. lock, flags);
  7. __pm_relax(dev->power.wakeup);
  8. spin_unlock_irqrestore(&dev->power. lock, flags);
  9. }

通过调用__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)函数实现


   
   
  1. void __pm_relax( struct wakeup_source *ws)
  2. {
  3. unsigned long flags;
  4. if (!ws)
  5. return;
  6. spin_lock_irqsave(&ws-> lock, flags);
  7. if (ws->active)
  8. wakeup_source_deactivate(ws);
  9. spin_unlock_irqrestore(&ws-> lock, flags);
  10. }

通过调用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)函数实现


   
   
  1. bool pm_get_wakeup_count(unsigned int *count, bool block)
  2. {
  3. unsigned int cnt, inpr;
  4. if (block) {
  5. DEFINE_WAIT(wait);
  6. for (;;) {
  7. prepare_to_wait(&wakeup_count_wait_queue, &wait,
  8. TASK_INTERRUPTIBLE);
  9. split_counters(&cnt, &inpr);
  10. if (inpr == 0 || signal_pending(current))
  11. break;
  12. pm_print_active_wakeup_sources();
  13. schedule();
  14. }
  15. finish_wait(&wakeup_count_wait_queue, &wait);
  16. }
  17. split_counters(&cnt, &inpr);
  18. *count = cnt;
  19. return !inpr;
  20. }

如果入参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)函数实现


   
   
  1. bool pm_wakeup_pending(void)
  2. {
  3. unsigned long flags;
  4. bool ret = false;
  5. raw_spin_lock_irqsave(&events_lock, flags);
  6. if (events_check_enabled) {
  7. unsigned int cnt, inpr;
  8. split_counters(&cnt, &inpr);
  9. ret = (cnt != saved_count || inpr > 0);
  10. events_check_enabled = !ret;
  11. }
  12. raw_spin_unlock_irqrestore(&events_lock, flags);
  13. if (ret) {
  14. pm_pr_dbg( "Wakeup pending, aborting suspend\n");
  15. pm_print_active_wakeup_sources();
  16. }
  17. return ret || atomic_read(&pm_abort_suspend) > 0;
  18. }

返回值有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的工作队列中继续走睡眠流程。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值