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

總體框架如下:

 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM