文章基于 www.wowotech.net 的学习内容
总体的框架
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 };
关于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);
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 }
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 }
前面的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 }
里面调用的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 }
以上就是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);
其 中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 }
关于 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 }
关于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 }
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 }
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 }
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 }
与之有关的函数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 }