总体的框架
wakeup events framework主要包括 wake lock, wakeup count, autosleep等机制
系统在suspend过程中的时候,当wakeup事件产生的时候,不能进入suspend状态
wakeup event framework就是解决用户空间和内核空间的同步问题的,包含下面情况
1. 驱动处理过程中,不允许进入suspend
2. 后续需要处理的用户进程,不会获取到wakeup events
3. 正在后续处理的用户进程,处理过程中,系统不能进入suspend
总体框架如下:
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结构
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: 上边两个接口的结合体,引入了时间控制
对于驱动设备常用的接口:
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之间的同步问题
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的主要实现:
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的主要实现
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函数:
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,主要是:
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
总体的框架
代码分析
wakeup_lock/wakeup_unlock的接口主要是下面的四个函数
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 表示
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 表示
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表示
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
总体的框架为:
1. sysfs关于autosleep的接口 /sys/power/autosleep
这个sysfs文件的读取 函数 autosleep_show:
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
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的状态
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
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