文章基於 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 }