Linux电源管理_wakeup event framework

文章基于 www.wowotech.net  的学习内容

总体的框架

wakeup events framework主要包括 wake lock, wakeup count, autosleep等机制

系统在suspend过程中的时候,当wakeup事件产生的时候,不能进入suspend状态

wakeup event framework就是解决用户空间和内核空间的同步问题的,包含下面情况

1. 驱动处理过程中,不允许进入suspend

2. 后续需要处理的用户进程,不会获取到wakeup events

3. 正在后续处理的用户进程,处理过程中,系统不能进入suspend

总体框架如下:

Linux电源管理_wakeup event framework

 wakeup events framework core就是linux关于wakeup event的核心框架,主要向驱动提供唤醒源注册,使能等接口。向上层提供上报,停止等接口,还有关于PM core的状态查询接口

sysfs文件

wake lock/unlock 提供给用户层面,可以阻止系统进入suspend的一个接口

wakeup count,用户上层用户查询wakeup event的一个接口

auto sleep就是设定系统没活动时,自动休眠的接口

 

关于wakeup source和wakeup event

1. 只有具有唤醒功能的设备才能作为wakeup source,具备唤醒功能的设备会被标识为唤醒能力,通过设备结构里面的can_wakeup标志标识,并且会在sysfs目录下有关于wakeup信息的文件存在

2. 具备唤醒功能的设备主要和 dev_pm_info结构有关

3. 一个wakeup source的设备,主要虚拟为结构体struct wakeup_source结构

Linux电源管理_wakeup event framework
 1 struct wakeup_source {
 2     const char         *name; // 设备名字
 3     struct list_head    entry;
 4     spinlock_t        lock;
 5     struct timer_list    timer;
 6     unsigned long        timer_expires;
 7     ktime_t total_time;
 8     ktime_t max_time;
 9     ktime_t last_time;
10     ktime_t start_prevent_time;
11     ktime_t prevent_sleep_time;
12     unsigned long        event_count; //设备产生wakeup event的个数
13     unsigned long        active_count; //产生wakeup event时,设备切换到active状态,这个     //表示了wakeup source设备的繁忙程度
14     unsigned long        relax_count;
15     unsigned long        expire_count;
16     unsigned long        wakeup_count; //中断进入suspend状态的次数
17     bool            active:1;
18     bool            autosleep_enabled:1;
19 };
View Code

关于wakeup event framework核心功能

1. __pm_stay_awake : wakeup source 切换为active状态的接口

2. __pm_relax: wakeup source 切换为disactive状态的接口

3. __pm_wakeup_event: 上边两个接口的结合体,引入了时间控制

对于驱动设备常用的接口:

Linux电源管理_wakeup event framework
 1 extern int device_wakeup_enable(struct device *dev); //使能wakeup功能
 2     dev->power.should_wakeup = true;
 3 extern int device_wakeup_disable(struct device *dev);
 4 extern void device_set_wakeup_capable(struct device *dev, bool capable);
 5     dev->power.can_wakeup = capable; //配置是否具备唤醒功能
 6 extern int device_init_wakeup(struct device *dev, bool val);//初始化wakeup功能
 7     device_set_wakeup_capable(dev, val);
 8     device_set_wakeup_enable(dev, val); 
 9 extern int device_set_wakeup_enable(struct device *dev, bool enable);
10     dev->power.should_wakeup = enable;
11 extern void pm_stay_awake(struct device *dev);
12     __pm_stay_awake(dev->power.wakeup);// 调用系统接口操作struct wakeup source变量,处理wakeup events
13 extern void pm_relax(struct device *dev);
14 extern void pm_wakeup_event(struct device *dev, unsigned int msec);
View Code

 

wakeup count

主要用于解决system suspend 和system wakeup events之间的同步问题

Linux电源管理_wakeup event framework

 wakeup count给上层提供了sysfs接口,给auto sleep提供了接口

实现的原理

1. 发生电源切换的实体先读取系统的wakeup count变量,并且告知wakeup events framework。

2. framework core保存这个变量到saved_count中

3. suspend过程中,有可能会发生wakeup events,所以某些时间点,会调用接口(pm_wakeup_pending),检查是否有wakeup需要处理

4. 如果有,代表读出来wakeup count 和saved_count不一样,这时需要终止suspend的过程

 

当调用类似 read(&cnt, "/sys/power/wakeup_count"); 的时候,系统最终会调用pm_get_wakeup_count

调用 write(cnt, "/sys/power/wakeup_count")的时候,系统最终会调用 pm_save_wakeup_count

pm_get_wakeup_count的主要实现:

Linux电源管理_wakeup event framework
 1 bool pm_get_wakeup_count(unsigned int *count, bool block)
 2 {
 3     unsigned int cnt, inpr;
 4 
 5     if (block) {
 6         DEFINE_WAIT(wait); // 定义等待队列
 7 
 8         for (;;) {
 9             prepare_to_wait(&wakeup_count_wait_queue, &wait,
10                     TASK_INTERRUPTIBLE);// 把wait加入等待队列链表里面,更改程序状态,一旦后面和wakeup_count_wait_queue相关的线程调用waitqueue_active就会遍历里面所有的wait,之后尝唤醒。
11             split_counters(&cnt, &inpr);
12             if (inpr == 0 || signal_pending(current)) //唤醒之后,等待inpr == 0
13                 break;
14 
15             schedule(); //条件不满足,继续睡眠
16         }
17         finish_wait(&wakeup_count_wait_queue, &wait); // 移除wait
18     }
19 
20     split_counters(&cnt, &inpr);
21     *count = cnt;
22     return !inpr;
23 }
View Code

pm_save_wakeup_count的主要实现

Linux电源管理_wakeup event framework
 1 bool pm_save_wakeup_count(unsigned int count)
 2 {
 3     unsigned int cnt, inpr;
 4     unsigned long flags;
 5 
 6     events_check_enabled = false; //这个变量为false代表wakeup count功能不使用
 7     spin_lock_irqsave(&events_lock, flags);
 8     split_counters(&cnt, &inpr);
 9     if (cnt == count && inpr == 0) { //满足所有disactive
10         saved_count = count; //保留count到saved_count 中
11         events_check_enabled = true;
12     }
13     spin_unlock_irqrestore(&events_lock, flags);
14     return events_check_enabled;
15 }
View Code

前面的suspend过程中,最后阶段会调用suspend_enter函数:

Linux电源管理_wakeup event framework
 1 static int suspend_enter(suspend_state_t state, bool *wakeup)
 2 {
 3     int error;
 4 
 5     ...
 6 
 7     error = syscore_suspend();
 8     if (!error) {
 9         *wakeup = pm_wakeup_pending(); //check wakeup events,false代表放心睡
10         if (!(suspend_test(TEST_CORE) || *wakeup)) {
11             error = suspend_ops->enter(state); // 如果没有wakeup events事件,那么进行suspend状态切换
12             events_check_enabled = false;
13         }
14         syscore_resume(); //否则中断suspend过程
15     }
16     ...
17     return error;
18 }
View Code

里面调用的pm_wakeup_pending,主要是:

Linux电源管理_wakeup event framework
 1 bool pm_wakeup_pending(void)
 2 {
 3     unsigned long flags;
 4     bool ret = false;
 5 
 6     spin_lock_irqsave(&events_lock, flags);
 7     if (events_check_enabled) {
 8         unsigned int cnt, inpr;
 9 
10         split_counters(&cnt, &inpr); //读wakeup count和in progress count
11         ret = (cnt != saved_count || inpr > 0);如果不等,代表有wakeup event产生
12         events_check_enabled = !ret;
13     }
14     spin_unlock_irqrestore(&events_lock, flags);
15 
16     if (ret)
17         print_active_wakeup_sources();
18 
19     return ret;
20 }
View Code

以上就是wakeup在用户层和suspend过程中的使用方式

 

wake_lock/wake_unlock

sysfs下的 /sys/power/wake_lock & /sys/power/wake_unlock

总体的框架

Linux电源管理_wakeup event framework

 

 代码分析

wakeup_lock/wakeup_unlock的接口主要是下面的四个函数

Linux电源管理_wakeup event framework
 1 static ssize_t wake_lock_show(struct kobject *kobj,
 2                   struct kobj_attribute *attr,
 3                   char *buf)
 4 {
 5     return pm_show_wakelocks(buf, true);
 6 }
 7 
 8 static ssize_t wake_lock_store(struct kobject *kobj,
 9                    struct kobj_attribute *attr,
10                    const char *buf, size_t n)
11 {
12     int error = pm_wake_lock(buf);
13     return error ? error : n;
14 }
15 
16 power_attr(wake_lock);
17 
18 static ssize_t wake_unlock_show(struct kobject *kobj,
19                 struct kobj_attribute *attr,
20                 char *buf)
21 {
22     return pm_show_wakelocks(buf, false);
23 }
24 
25 static ssize_t wake_unlock_store(struct kobject *kobj,
26                  struct kobj_attribute *attr,
27                  const char *buf, size_t n)
28 {
29     int error = pm_wake_unlock(buf);
30     return error ? error : n;
31 }
32 
33 power_attr(wake_unlock);
View Code

其 中pm_show_wakelocks 表示

Linux电源管理_wakeup event framework
 1 ssize_t pm_show_wakelocks(char *buf, bool show_active)
 2 {
 3     struct rb_node *node;
 4     struct wakelock *wl;
 5     char *str = buf;
 6     char *end = buf + PAGE_SIZE;
 7 
 8     mutex_lock(&wakelocks_lock);
 9 
10     for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) { //遍历红黑树
11         wl = rb_entry(node, struct wakelock, node);
12         if (wl->ws.active == show_active)// 找满足show_active状态
13             str += scnprintf(str, end - str, "%s ", wl->name); // 把对应的wakeup_lock的名字
14     }
15     if (str > buf)
16         str--;
17 
18     str += scnprintf(str, end - str, "\n");
19 
20     mutex_unlock(&wakelocks_lock);
21     return (str - buf);
22 }
View Code

关于 pm_wake_lock 表示

Linux电源管理_wakeup event framework
 1 int pm_wake_lock(const char *buf)
 2 {
 3     const char *str = buf;
 4     struct wakelock *wl;
 5     u64 timeout_ns = 0;
 6     size_t len;
 7     int ret = 0;
 8 
 9     if (!capable(CAP_BLOCK_SUSPEND)) //判断当前进程是否有权限
10         return -EPERM;
11 
12     while (*str && !isspace(*str))
13         str++;
14 
15     len = str - buf;
16     if (!len)
17         return -EINVAL;
18 
19     if (*str && *str != \n) {
20         /* Find out if there‘s a valid timeout string appended. */
21         ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
22         if (ret)
23             return -EINVAL;
24     }
25 
26     mutex_lock(&wakelocks_lock);
27 
28     wl = wakelock_lookup_add(buf, len, true); // 查找是否有相同名字的wakeuplock
29         // 主要根据传进来的buf里面的name和红黑树里面每个node里面的名字进行比较,有则返回对应的指针
30         // 没有则分配空间,并且把传进来的buf里面的wakeuplock信息加入到红黑树里面
31     if (IS_ERR(wl)) {
32         ret = PTR_ERR(wl);
33         goto out;
34     }
35     if (timeout_ns) { // 如果定义了timeout,通过修改定时器,上报一个具有时限的wakeup_event
36         u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
37 
38         do_div(timeout_ms, NSEC_PER_MSEC);
39         __pm_wakeup_event(&wl->ws, timeout_ms);
40     } else {//否则上报一个没有时限的wakeup_event
41         __pm_stay_awake(&wl->ws);
42     }
43 
44     wakelocks_lru_most_recent(wl);
45 
46  out:
47     mutex_unlock(&wakelocks_lock);
48     return ret;
49 }
View Code

关于pm_wake_unlock表示

Linux电源管理_wakeup event framework
 1 int pm_wake_unlock(const char *buf)
 2 {
 3     struct wakelock *wl;
 4     size_t len;
 5     int ret = 0;
 6 
 7     if (!capable(CAP_BLOCK_SUSPEND))
 8         return -EPERM;
 9 
10     len = strlen(buf);
11     if (!len)
12         return -EINVAL;
13 
14     if (buf[len-1] == \n)
15         len--;
16 
17     if (!len)
18         return -EINVAL;
19 
20     mutex_lock(&wakelocks_lock);
21 
22     wl = wakelock_lookup_add(buf, len, false); //查找红黑树里面有没有符合条件的wakeuplock
23     if (IS_ERR(wl)) {
24         ret = PTR_ERR(wl);
25         goto out;
26     }
27     __pm_relax(&wl->ws);//deactive对应的wakesource
28 
29     wakelocks_lru_most_recent(wl);
30     wakelocks_gc();
31 
32  out:
33     mutex_unlock(&wakelocks_lock);
34     return ret;
35 }
View Code

wakelock的垃圾回收机制

主要考虑到wakeup events 建立,销毁,建立的过程太频繁,效率就会降低,所以引入了wakeuplock的垃圾回收机制

主要的原理是:

先保留一些非active状态的wakelocks,等保留的wakelock的数量到达某一个定义的最大值时,则从尾部开始,依次取出wakelock,判断idle的时间,进行注销和释放memory资源

 

Auto Sleep

概念:

当系统没有了正在处理和新增的wakeup events时,就尝试suspend

总体的框架为:

Linux电源管理_wakeup event framework

1. sysfs关于autosleep的接口  /sys/power/autosleep

这个sysfs文件的读取 函数 autosleep_show:

Linux电源管理_wakeup event framework
 1 #ifdef CONFIG_PM_AUTOSLEEP
 2 static ssize_t autosleep_show(struct kobject *kobj,
 3                   struct kobj_attribute *attr,
 4                   char *buf)
 5 {
 6     suspend_state_t state = pm_autosleep_state(); //获取当前系统 state,主要包括“freeze”,“standby”,“mem”,“disk”, “off”,“error”等6个字符串
 7 
 8     if (state == PM_SUSPEND_ON)
 9         return sprintf(buf, "off\n");
10 
11 #ifdef CONFIG_SUSPEND
12     if (state < PM_SUSPEND_MAX)
13         return sprintf(buf, "%s\n", valid_state(state) ?
14                         pm_states[state] : "error");
15 #endif
16 #ifdef CONFIG_HIBERNATION
17     return sprintf(buf, "disk\n");
18 #else
19     return sprintf(buf, "error");
20 #endif
21 }
View Code

2. 关于autosleep的初始化

关于 pm_autosleep_init 

Linux电源管理_wakeup event framework
 1 int __init pm_autosleep_init(void)
 2 {
 3     autosleep_ws = wakeup_source_register("autosleep"); //创建wakesource并且加到对应的链表里面
 4         ws = wakeup_source_create(name);
 5          wakeup_source_add(ws);
 6     if (!autosleep_ws)
 7         return -ENOMEM;
 8 
 9     autosleep_wq = alloc_ordered_workqueue("autosleep", 0); //创建一个有序的工作队列,用于触发主要的休眠操作
10     if (autosleep_wq)
11         return 0;
12 
13     wakeup_source_unregister(autosleep_ws);
14     return -ENOMEM;
15 }
View Code

3. 设置 autosleep的状态

Linux电源管理_wakeup event framework
 1 int pm_autosleep_set_state(suspend_state_t state)
 2 {
 3 
 4 #ifndef CONFIG_HIBERNATION
 5     if (state >= PM_SUSPEND_MAX)
 6         return -EINVAL;
 7 #endif
 8 
 9     __pm_stay_awake(autosleep_ws); // active这个系统,不允许进入suspend
10 
11     mutex_lock(&autosleep_lock);
12 
13     autosleep_state = state; // 更新系统当前状态
14 
15     __pm_relax(autosleep_ws); //运行系统进入休眠
16 
17     if (state > PM_SUSPEND_ON) {
18         pm_wakep_autosleep_enabled(true); // autosleep enable
19         queue_up_suspend_work(); //将suspend work挂到 autosleep工作队列里面
20     } else {
21         pm_wakep_autosleep_enabled(false);
22     }
23 
24     mutex_unlock(&autosleep_lock);
25     return 0;
26 }
View Code

与之有关的函数pm_wakep_autosleep_enabled

Linux电源管理_wakeup event framework
 1 void pm_wakep_autosleep_enabled(bool set)
 2 {
 3     struct wakeup_source *ws;
 4     ktime_t now = ktime_get();
 5 
 6     rcu_read_lock();
 7     list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
 8         spin_lock_irq(&ws->lock);
 9         if (ws->autosleep_enabled != set) {
10             ws->autosleep_enabled = set;//更新和autosleep相关的所有状态
11             if (ws->active) {
12                 if (set)
13                     ws->start_prevent_time = now; //设置为当前实现,马上阻止进入autosleep
14                 else
15                     update_prevent_sleep_time(ws, now);
16             }
17         }
18         spin_unlock_irq(&ws->lock);
19     }
20     rcu_read_unlock();
21 }
View Code

 

Linux电源管理_wakeup event framework

上一篇:每天学五分钟 Liunx 0111 | 服务篇:进程权限


下一篇:一篇忘了的内容 教材:《Linux就该这么学》,打个广告^_^