Linux电源管理-wakeup count

前言

在wakeup events framework小节中提到,wakeup events framwork可以解决system suspend和wakeup events之间的同步问题。而整篇下来没有看到是如何解决同步问题的。所有本小节继续分析wakeup events framework中的重要知识点-wakeup count。

"wakeup count"是不是很熟悉?  是的,在wakeup_source结构体中就存在"wakeup_count"成员,此成员的意思是:终止suspend的次数。而本小节的wakeup count并非此意,只是名字相同罢了。:(

实现原理

1.   在进行suspend之前,需要先获取系统中总的wakeup event数量。
2.   将获得的值保存到全局变量saved_count中。
3.   此后可能系统已经进入了suspend的流程中。这时候如果系统发生了wakeup events,就会增加wakeup event的数量。
4.   在suspend执行的过程中,会调用pm_wakeup_pending接口检测系统有没有发生wakeup event。(通过比较当前的wakeup events和之前保存的值saved_count是否相同)
5.   如果不同,则终止系统suspend。否则继续执行suspend流程。

那wakeup event framework是如何保存当前系统中所有的wakeup event?  以及如何如何判断当前是否有wake events正在处理?
通常思路:  用一个变量记录当前系统发生的所有wakeup event,用另一个变量记录当前是否有wake events在处理。
那Linux内核到底是如何记录这两个变量呢? 

linux中使用一个原子变量,高16位记录系统所有的wakeup event总数,低16位记录是否有wakeup events在处理中。
  1. /* 
  2.  * Combined counters of registered wakeup events and wakeup events in progress. 
  3.  * They need to be modified together atomically, so it's better to use one 
  4.  * atomic variable to hold them both. 
  5.  */  
  6. static atomic_t combined_event_count = ATOMIC_INIT(0);  
  7.   
  8. #define IN_PROGRESS_BITS    (sizeof(int) * 4)  
  9. #define MAX_IN_PROGRESS     ((1 << IN_PROGRESS_BITS) - 1)  
  10.   
  11. static void split_counters(unsigned int *cnt, unsigned int *inpr)  
  12. {  
  13.     unsigned int comb = atomic_read(&combined_event_count);  
  14.   
  15.     *cnt = (comb >> IN_PROGRESS_BITS);  
  16.     *inpr = comb & MAX_IN_PROGRESS;  
  17. }  
"registered wakeup events"代表系统自启动以来所有的wakeup event的总数,在combined_event_count的高16位。
"wakeup event in progress"代表系统是否有wake events正在处理,在combined_event_count的低16位。

当系统有wakeup events上报时,调用wakeup events framework的接口active该wakeup source,然后"wakeup event in progress"加1。
  1. static void wakeup_source_activate(struct wakeup_source *ws)  
  2. {  
  3.     unsigned int cec;  
  4.   
  5.     /* 
  6.      * active wakeup source should bring the system 
  7.      * out of PM_SUSPEND_FREEZE state 
  8.      */  
  9.     freeze_wake();  
  10.   
  11.     ws->active = true;  
  12.     ws->active_count++;  
  13.     ws->last_time = ktime_get();  
  14.     if (ws->autosleep_enabled)  
  15.         ws->start_prevent_time = ws->last_time;  
  16.   
  17.     /* Increment the counter of events in progress. */  
  18.     cec = atomic_inc_return(&combined_event_count);  
  19.   
  20.     trace_wakeup_source_activate(ws->name, cec);  
  21. }  
那什么时候"registered wakeup events"加1呢?  答案是在"wakeup event in progress"减1的时候,"registered wakeup events"加1。因为这时候一个wakeup event刚处理完毕,就代表系统已经发生过一个wakeup event。
  1. /* 
  2.  * Increment the counter of registered wakeup events and decrement the 
  3.  * couter of wakeup events in progress simultaneously. 
  4.  */  
  5. cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);  
当然了此处的注释写的也很清楚。

那接着再用linux中的实现方法回答上述的问题:   wakeup event framework是如何保存当前系统中所有的wakeup event?  以及如何如何判断当前是否有wake events正在处理?
1.  获取combined_event_count的高16位就可以知道有多少wakeup event。
2.  判断combined_event_count的低16位是否为零,就知道有没有wakeup event在处理。

实现流程

既然明白了上述的原理,就按照此原理一步一步分析代码的处理流程即可。
a.  在进行suspend操作之前,需要获取wakeup event的数量。之前说过wakeup event的数量存在combined_event_count的高16位,而获取该值可以通过split_counters接口,所以可以直接搜索该接口即可。
  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);  
  11.             split_counters(&cnt, &inpr);  
  12.             if (inpr == 0 || signal_pending(current))  
  13.                 break;  
  14.   
  15.             schedule();  
  16.         }  
  17.         finish_wait(&wakeup_count_wait_queue, &wait);  
  18.     }  
  19.   
  20.     split_counters(&cnt, &inpr);  
  21.     *count = cnt;  
  22.     return !inpr;  
  23. }  
1. 如果block为false, 直接通过split_counters就可以获取wakeup event的总数,存在count返回。同时返回"wakeup event in progress"的状态,如果返回false,说明有wakeup events在处理,则不允许suspend,否则可以。
2. 如果block为ture,就会定义一个wait队列,等待"wakeup in event progress"为0,然后在返回count。

b.  获得到当前的"wakeup event"总数后,就需要将此值存到全局变量saved_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;  
  7.     spin_lock_irqsave(&events_lock, flags);  
  8.     split_counters(&cnt, &inpr);  
  9.     if (cnt == count && inpr == 0) {  
  10.         saved_count = count;  
  11.         events_check_enabled = true;  
  12.     }  
  13.     spin_unlock_irqrestore(&events_lock, flags);  
  14.     return events_check_enabled;  
  15. }  
1. 首先获取"wakeup event in progress"和"register wakeup event"的值。
2.  然后比较传入的count是否等于"register wakeup event" 同时"wakeup event in progress"需要等于0。如果都不满足,说明在存储之前发生了wakeup event。
3.  置位events_check_enabled的值为true。如果此值为false,wakeup event 检测机制就会不起作用的。

c. 假设在suspend的过程中,发生了wakeup event事件。同时上报到wakeup event framework。
d. 在susupend的流程中,就会调用pm_wakeup_pending接口检测是否有wakeup event发生。比如如下代码:
  1. error = syscore_suspend();  
  2. if (!error) {  
  3.     *wakeup = pm_wakeup_pending();  
  4.     if (!(suspend_test(TEST_CORE) || *wakeup)) {  
  5.         trace_suspend_resume(TPS("machine_suspend"),  
  6.             state, true);  
  7.         error = suspend_ops->enter(state);  
  8.         trace_suspend_resume(TPS("machine_suspend"),  
  9.             state, false);  
  10.         events_check_enabled = false;  
  11.     } else if (*wakeup) {  
  12.         pm_get_active_wakeup_sources(suspend_abort,  
  13.             MAX_SUSPEND_ABORT_LEN);  
  14.         log_suspend_abort_reason(suspend_abort);  
  15.         error = -EBUSY;  
  16.     }  
  17.     syscore_resume();  
  18. }  
在suspend的最后阶段会再次调用pending接口检测是否有wakeup event发生的。
  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);  
  11.         ret = (cnt != saved_count || inpr > 0);  
  12.         events_check_enabled = !ret;  
  13.     }  
  14.     spin_unlock_irqrestore(&events_lock, flags);  
  15.   
  16.     if (ret) {  
  17.         pr_info("PM: Wakeup pending, aborting suspend\n");  
  18.         pm_print_active_wakeup_sources();  
  19.     }  
  20.   
  21.     return ret || pm_abort_suspend;  
  22. }  
1.  判断events_check_enabled是否为true,如果为false,就不会abort suspend流程。
2.  如果events_check_enabled为true,获取"registered wakeup event"和"wakeup event in progress"的值。判断registered wakeup event的值是否和saved_count的值不等,且wakeup event in progress大于0,说明有新的wakeup event发生,不能suspend,清除event_check_enabled标志。
3.  否则registered wakeup event等于saved_count且wakeup event in progress等于0,说明没有新的wakeup event发生,继续睡眠。

猜你喜欢

转载自blog.csdn.net/tuyerv/article/details/79816862
今日推荐