Wakeup Source 為系統組件提供了投票機制,以便低功耗子系統判斷當前是否可以進入休眠。
Wakeup Source(后簡稱:WS) 模塊可與內核中的其他模塊或者上層服務交互,并最終體現在對睡眠鎖的控制上。
1. 模塊功能說明
WS的處理邏輯基本上是圍繞?combined_event_count?變量展開的,這個變量高16位記錄系統已處理的所有的喚醒事件總數,低16位記錄在處理中的喚醒事件總數。每次持鎖時,處理中的喚醒事件記錄(低16位)會加1;每次釋放鎖時,處理中的喚醒事件記錄(低16位)會減1,同時已處理的喚醒事件記錄(高16位)會加1。
對于每次系統能否進入休眠,通過判斷是否有正在處理中的喚醒事件(低16位)來決定。該模塊實現主要的功能:
-
持鎖和釋放鎖
-
注冊和注銷鎖
-
查詢激活狀態鎖個數
2. 主要數據結構
2.1 wakeup_source 結構體
@include/linux/pm_wakeup.h
/*** struct wakeup_source - Representation of wakeup sources** @name: Name of the wakeup source* @id: Wakeup source id* @entry: Wakeup source list entry* @lock: Wakeup source lock* @wakeirq: Optional device specific wakeirq* @timer: Wakeup timer list* @timer_expires: Wakeup timer expiration* @total_time: Total time this wakeup source has been active.* @max_time: Maximum time this wakeup source has been continuously active.* @last_time: Monotonic clock when the wakeup source's was touched last time.* @prevent_sleep_time: Total time this source has been preventing autosleep.* @event_count: Number of signaled wakeup events.* @active_count: Number of times the wakeup source was activated.* @relax_count: Number of times the wakeup source was deactivated.* @expire_count: Number of times the wakeup source's timeout has expired.* @wakeup_count: Number of times the wakeup source might abort suspend.* @dev: Struct device for sysfs statistics about the wakeup source.* @active: Status of the wakeup source.* @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time.*/
struct wakeup_source {const char *name; //ws 名稱int id; //WS系統給本ws分配的IDstruct list_head entry; //用于把本ws節點維護到WS系統的全局鏈表中spinlock_t lock;struct wake_irq *wakeirq; //與本ws節點綁定的喚醒中斷相關的結構體,用戶可自行把指定中斷與ws綁定struct timer_list timer; //超時鎖使用,如定義本ws為超時鎖,指定在一定時間后釋放鎖unsigned long timer_expires;//超時鎖超時時間ktime_t total_time; //本ws激活的總時長ktime_t max_time; //在ws激活歷史中,最長一次的激活時間ktime_t last_time; //最后一次訪問本ws的時間ktime_t start_prevent_time; //本ws最近一次阻止autosleep進入休眠的時間戳ktime_t prevent_sleep_time; //因本ws導致的阻止autosleep進入休眠的總時間unsigned long event_count; //事件次數,本ws被持鎖(不考慮是否已持鎖),則加1并作記錄unsigned long active_count;//激活次數,本ws僅在首次持鎖(激活)時加1(已持鎖則不加1,鎖釋放后再次持鎖則加1)unsigned long relax_count; //釋放次數,與 active_count 相對unsigned long expire_count; //超時鎖超時次數unsigned long wakeup_count; //與event_count一樣,但受events_check_enabled 使能標記控制struct device *dev; //與本ws綁定的設備bool active:1; //標記是否處于激活狀態bool autosleep_enabled:1; //標記是否使能autosleep
};
2.2 核心變量
2.2.1 combined_event_count 變量
static atomic_t combined_event_count = ATOMIC_INIT(0);
該變量是1個組合計數變量,高16位記錄喚醒事件的總數,低16位記錄正在處理中的喚醒事件的總數。系統根據低16位(正在處理中的喚醒事件)來判斷是否可以進入休眠。
2.2.2 wakeup_sources 變量
static LIST_HEAD(wakeup_sources);
所有通過調用?wakeup_source_register()
注冊的ws全部維護在此鏈表中,以便系統進行維護。
2.3 主要函數分析
Wakeup Source 對外提供的主要接口:
-
wakeup_source_register()
與wakeup_source_unregister()
分別用于注冊與注銷一個ws -
__pm_stay_awake()
與__pm_relax()
,針對ws類型對象提供持鎖與釋放鎖接口 -
(
device_set_wakeup_capable()
+device_wakeup_enable()
/device_wakeup_disable()
/device_set_wakeup_enable()
)/device_init_wakeup()
給設備配置是否支持喚醒以及注冊/注銷ws的接口 -
pm_stay_awake()
與pm_relax()
,針對device類型對象提供持鎖與釋放鎖接口
2.3.1 wakeup_source_register()/wakeup_source_unregister() 接口
wakeup_source_register()
函數為dev設備創建ws,并將創建的ws添加到全局鏈表wakeup_sources
中,方便后續維護,并在sysfs系統中創建節點/sys/class/wakeup/wakeup<id>/
,便于獲取ws相關信息。
@drivers/base/power/wakeup.c
/*** wakeup_source_register - Create wakeup source and add it to the list.* @dev: Device this wakeup source is associated with (or NULL if virtual).* @name: Name of the wakeup source to register.*/
struct wakeup_source *wakeup_source_register(struct device *dev,const char *name)
{struct wakeup_source *ws;int ret;ws = wakeup_source_create(name); //分配內存,設置ws的name和idif (ws) {if (!dev || device_is_registered(dev)) {//在sysfs下為該ws創建dev, /sys/class/wakeup/wakeup<id>/ret = wakeup_source_sysfs_add(dev, ws);if (ret) {wakeup_source_free(ws);return NULL;}}wakeup_source_add(ws); //設置超時回調函數并將ws添加到wakeup_sources鏈表}return ws;
}
@drivers/base/power/wakeup_stats.c
static struct device *wakeup_source_device_create(struct device *parent,struct wakeup_source *ws)
{struct device *dev = NULL;int retval = -ENODEV;dev = kzalloc(sizeof(*dev), GFP_KERNEL);device_initialize(dev);dev->devt = MKDEV(0, 0);dev->class = wakeup_class; //ws dev掛于wakeup類dev->parent = parent;dev->groups = wakeup_source_groups;dev->release = device_create_release;dev_set_drvdata(dev, ws);device_set_pm_not_required(dev);retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id);retval = device_add(dev);return dev;
}
//ws dev存在的屬性: /sys/class/wakeup/wakeup<id>/
static struct attribute *wakeup_source_attrs[] = {&dev_attr_name.attr, //RO, ws 名稱&dev_attr_active_count.attr, //RO, 激活次數&dev_attr_event_count.attr, //RO, 持鎖次數&dev_attr_wakeup_count.attr, //RO, 同event_count,但受events_check_enabled使能標記&dev_attr_expire_count.attr, //RO, 超時次數&dev_attr_active_time_ms.attr, //RO, 如當前處于激活狀態,顯示已激活時間&dev_attr_total_time_ms.attr, //RO, 總激活時間&dev_attr_max_time_ms.attr, //RO, 最長激活時間&dev_attr_last_change_ms.attr, //RO, 最近一次激活時的時間戳&dev_attr_prevent_suspend_time_ms.attr, //RO, 阻止autosleep進入休眠的總時間NULL,
};
ATTRIBUTE_GROUPS(wakeup_source);
wakeup_source_unregister()
?接口刪除了已注冊的ws,移除了sysfs系統中的節點并釋放占用的系統資源。
@drivers/base/power/wakeup.c
void wakeup_source_unregister(struct wakeup_source *ws)
{if (ws) {wakeup_source_remove(ws); //從wakeup_sources隊列移除并刪除其定時器if (ws->dev)wakeup_source_sysfs_remove(ws);//移除該ws在sysfs系統中的信息wakeup_source_destroy(ws);}
}
void wakeup_source_destroy(struct wakeup_source *ws)
{__pm_relax(ws); //釋放該wswakeup_source_record(ws);//如果該ws被持鎖過,則將其記錄疊加到deleted_ws這個ws上wakeup_source_free(ws);//釋放內存資源
}static struct wakeup_source deleted_ws = {//用于保存已移除ws的記錄.name = "deleted",.lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock),
};static void wakeup_source_record(struct wakeup_source *ws)
{unsigned long flags;spin_lock_irqsave(&deleted_ws.lock, flags);if (ws->event_count) {//如果該ws被持鎖過,則將記錄都疊加到deleted_ws這個ws上deleted_ws.total_time =ktime_add(deleted_ws.total_time, ws->total_time);deleted_ws.prevent_sleep_time =ktime_add(deleted_ws.prevent_sleep_time,ws->prevent_sleep_time);deleted_ws.max_time =ktime_compare(deleted_ws.max_time, ws->max_time) > 0 ?deleted_ws.max_time : ws->max_time;deleted_ws.event_count += ws->event_count;deleted_ws.active_count += ws->active_count;deleted_ws.relax_count += ws->relax_count;deleted_ws.expire_count += ws->expire_count;deleted_ws.wakeup_count += ws->wakeup_count;}spin_unlock_irqrestore(&deleted_ws.lock, flags);
}
2.3.2 __pm_stay_awake()/__pm_relax() 接口
__pm_stay_awake()
?用于上鎖ws來阻止系統休眠。
@drivers/base/power/wakeup.c
void __pm_stay_awake(struct wakeup_source *ws)
{unsigned long flags;if (!ws)return;spin_lock_irqsave(&ws->lock, flags);wakeup_source_report_event(ws, false);//紀錄該ws的信息del_timer(&ws->timer);ws->timer_expires = 0;spin_unlock_irqrestore(&ws->lock, flags);
}
static void wakeup_source_report_event(struct wakeup_source *ws, bool hard)
{ws->event_count++; //持鎖次數加1/* This is racy, but the counter is approximate anyway. */if (events_check_enabled)ws->wakeup_count++;if (!ws->active) //ws還未激活情況下,激活wswakeup_source_activate(ws);if (hard) //如果需要,可以強制阻止系統休眠pm_system_wakeup();
}
static void wakeup_source_activate(struct wakeup_source *ws)
{unsigned int cec;if (WARN_ONCE(wakeup_source_not_registered(ws),"unregistered wakeup source\n"))return;ws->active = true;ws->active_count++; //激活次數加1ws->last_time = ktime_get(); //紀錄最后操作該鎖的時間戳if (ws->autosleep_enabled) //如果autosleep已使能,則記錄該ws阻止休眠時時間戳ws->start_prevent_time = ws->last_time;/* Increment the counter of events in progress. */cec = atomic_inc_return(&combined_event_count); //combined_event_count低16位加1trace_wakeup_source_activate(ws->name, cec);
}
__pm_relax()
?用于將持有的睡眠鎖釋放掉,并在檢測到combined_event_count
低16位為0(表示當前沒有在處理的ws)時會觸發wakeup_count_wait_queue
等待隊列運行,如果工作隊列滿足睡眠條件,則繼續進入睡眠流程,該機制是通過pm_get_wakeup_count()
接口與autosleep配合使用的
@drivers/base/power/wakeup.c
void __pm_relax(struct wakeup_source *ws)
{unsigned long flags;if (!ws)return;spin_lock_irqsave(&ws->lock, flags);if (ws->active) //如果ws已激活,則去激活該wswakeup_source_deactivate(ws);spin_unlock_irqrestore(&ws->lock, flags);
}static void wakeup_source_deactivate(struct wakeup_source *ws)
{unsigned int cnt, inpr, cec;ktime_t duration;ktime_t now;ws->relax_count++; //釋放次數加1/** __pm_relax() may be called directly or from a timer function.* If it is called directly right after the timer function has been* started, but before the timer function calls __pm_relax(), it is* possible that __pm_stay_awake() will be called in the meantime and* will set ws->active. Then, ws->active may be cleared immediately* by the __pm_relax() called from the timer function, but in such a* case ws->relax_count will be different from ws->active_count.*/if (ws->relax_count != ws->active_count) {ws->relax_count--; //未解決定時鎖與主動調用釋放鎖并發操作時出現沖突做的處理return;}ws->active = false;now = ktime_get();duration = ktime_sub(now, ws->last_time);ws->total_time = ktime_add(ws->total_time, duration); //疊加總的持鎖時間if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))ws->max_time = duration; //更新最長持鎖時間ws->last_time = now; //紀錄最后操作該鎖的時間戳del_timer(&ws->timer);ws->timer_expires = 0;if (ws->autosleep_enabled)//如果autosleep已使能,更新該ws阻止系統休眠的時長update_prevent_sleep_time(ws, now);/** Increment the counter of registered wakeup events and decrement the* couter of wakeup events in progress simultaneously.*/cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);//combined_event_count高16位加1trace_wakeup_source_deactivate(ws->name, cec);split_counters(&cnt, &inpr);//拆分出combined_event_count高16位和低16位if (!inpr && waitqueue_active(&wakeup_count_wait_queue))//如果該ws已經無正在處理的喚醒事件,則通知PM corewake_up(&wakeup_count_wait_queue);
}
注:同個ws連續使用多次__pm_stay_awake()
或__pm_relax()
只會增加/減少一次combined_event_count
低16位(表示正在處理中的事件總數),只要__pm_relax()
被調用就會釋放鎖。
2.3.3?pm_get_wakeup_count()
接口
該函數主要是獲取已處理的wakeup event數量(combined_event_count
高16位)與正在處理的wakeup event數量是否為0(combined_event_count
低16位)。
bool pm_get_wakeup_count(unsigned int *count, bool block)
{unsigned int cnt, inpr;if (block) { DEFINE_WAIT(wait); //定義名為wait的等待隊列入口for (;;) {prepare_to_wait(&wakeup_count_wait_queue, &wait,TASK_INTERRUPTIBLE); //準備 wakeup_count_wait_queue 等待隊列split_counters(&cnt, &inpr);if (inpr == 0 || signal_pending(current))break;pm_print_active_wakeup_sources();schedule(); //調度到其他線程}//__pm_relax() 里wake_up(&wakeup_count_wait_queue);會觸發調度到此處finish_wait(&wakeup_count_wait_queue, &wait);}split_counters(&cnt, &inpr);*count = cnt;return !inpr; //返回0表示有待處理事件,返回1表示無待處理事件
}
1.如果入參
block
為0,則僅僅對入參count
賦值當前已處理的wakeup event總數,并返回當前是否有待處理wakeup event(返回0表示有待處理事件,返回1表示無待處理事件)。2.如果入參block
為1,則需要一直等到待處理事件為0(combined_event_count
低16位為0)或者當前掛起進程有事件需要處理時才退出。該處理分支的wait
等待隊列會在__pm_relax()
滿足睡眠條件時觸發調度運行,即finish_wait()
.
2.3.4 pm_wakeup_pending() 接口
該函數的功能是確認當前是否滿足休眠條件,返回true表示可以休眠,false表示不可休眠。
bool pm_wakeup_pending(void)
{unsigned long flags;bool ret = false;raw_spin_lock_irqsave(&events_lock, flags);if (events_check_enabled) {unsigned int cnt, inpr;split_counters(&cnt, &inpr);ret = (cnt != saved_count || inpr > 0);events_check_enabled = !ret;}raw_spin_unlock_irqrestore(&events_lock, flags);if (ret) {pm_pr_dbg("Wakeup pending, aborting suspend\n");pm_print_active_wakeup_sources();}return ret || atomic_read(&pm_abort_suspend) > 0;
}
判斷允許休眠的依據:1.已處理的wakeup event數量與已記錄的數量(saved_count)一致,且2.待處理的wakeup event數量為0,且3.原子量pm_abort_suspend
為0(該值大于0表示睡眠流程中出現了喚醒中斷或事件,喚醒事件通過調用pm_system_wakeup()
來給pm_abort_suspend
加1操作。)
2.3.5 device與wakeup_source關聯處理的接口
kernel抽象出的device數據結構存放著power manager相關的信息,其中就存放著wakeup source數據結構,如下:
//代碼格式錯誤,僅為呈現數據結構,請忽略格式。
struct device {// @power: For device power management.struct dev_pm_info power {unsigned int can_wakeup:1; //需置1才允許使用wakeup sourcestruct wakeup_source *wakeup; };
};
wakeup source框架中為此提供了大量相關的接口直接操作某個dev的ws,接口如下:
-
int device_wakeup_enable(struct device *dev)
:注冊設備的wakeup source1.以dev名注冊個ws,并指定該ws dev的parent為當前dev2.將注冊的ws關聯到dev->power.wakeup,如果存在wakeirq,也會一起綁定到該ws上。 -
int device_wakeup_disable(struct device *dev)
:注銷設備的wakeup source1.取消已注冊的ws與dev->power.wakeup的關聯2.注銷ws -
void device_set_wakeup_capable(struct device *dev, bool capable)
:設置設備是否支持wakeup source1.設置dev->power.can_wakeup2.如果設備支持wakeup,則為其創建屬性文件(位于/sys/devices/<dev_name>/power/下);如果設備不支持wakeup,則不會移除相關屬性文件。
static struct attribute *wakeup_attrs[] = {
#ifdef CONFIG_PM_SLEEP&dev_attr_wakeup.attr, //RW,可寫入enabled/disabled動態配置是否支持wakeup&dev_attr_wakeup_count.attr, //RO, 讀取該dev ws的wakeup_count&dev_attr_wakeup_active_count.attr, //RO, 讀取該dev ws的active_count&dev_attr_wakeup_abort_count.attr, //RO, 讀取該dev ws的wakeup_count&dev_attr_wakeup_expire_count.attr, //RO, 讀取該dev ws的expire_count&dev_attr_wakeup_active.attr, //RO, 讀取該dev ws的active狀態&dev_attr_wakeup_total_time_ms.attr, //RO, 讀取該dev ws的total_time&dev_attr_wakeup_max_time_ms.attr, //RO, 讀取該dev ws的max_time&dev_attr_wakeup_last_time_ms.attr, //RO, 讀取該dev ws的last_time
#ifdef CONFIG_PM_AUTOSLEEP&dev_attr_wakeup_prevent_sleep_time_ms.attr, //RO, 讀取該dev ws的prevent_sleep_time
#endif
#endifNULL,
};
-
int device_init_wakeup(struct device *dev, bool enable)
:一步到位直接配置是否支持wakeup并且注冊/注銷ws
int device_init_wakeup(struct device *dev, bool enable)
{int ret = 0;if (enable) {device_set_wakeup_capable(dev, true);ret = device_wakeup_enable(dev);} else {device_wakeup_disable(dev);device_set_wakeup_capable(dev, false); }return ret;
}
-
int device_set_wakeup_enable(struct device *dev, bool enable)
:設置設備是否能通過ws喚醒系統,注冊/注銷ws
int device_set_wakeup_enable(struct device *dev, bool enable)
{return enable ? device_wakeup_enable(dev) : device_wakeup_disable(dev);
}
-
void pm_stay_awake(struct device *dev)
:持鎖設備的ws,不讓設備休眠,實際是調用__pm_stay_awake(dev->power.wakeup);
實現 -
void pm_relax(struct device *dev)
:釋放設備的ws,允許設備休眠,實際是調用__pm_relax(dev->power.wakeup);
實現
總結:1.
device_set_wakeup_capable()
?用于設置是否支持wakeup,并提供屬性節點,便于調試2.device_wakeup_enable()
/device_wakeup_disable()
/device_set_wakeup_enable()
主要是注冊/注銷設備ws,需在device_set_wakeup_capable()
為enabled的前提下才能使用。3.device_init_wakeup()
?通常使用在默認支持wakeup的device上,在probe/remove時分別enable/disable。4.pm_stay_awake()
/pm_relax()
主要是持有/釋放ws鎖,阻止/允許系統休眠
3. 主要工作時序
1)device或者其他需要上鎖的模塊調用device_init_wakeup()
/wakeup_source_register()
來注冊ws2)在處理業務時,為了防止系統進入睡眠流程,設備或模塊可以通過調用pm_stay_awake()
/__pm_stay_awake()
來持鎖ws阻止休眠3)當業務處理完成后,設備或模塊可以調用pm_relax()
/__pm_relax()
來釋放ws允許系統休眠4)在__pm_relax()
釋放鎖時,會檢查當前是否有正在處理的持鎖事件,如果沒有,則觸發wakeup_count_wait_queue
5)wakeup_count_wait_queue
所在的pm_get_wakeup_count()
接口會返回到autosleep的工作隊列中繼續走休眠流程
4. 調試節點
-
獲取所有wakeup source信息節點:
cat /d/wakeup_sources
列出所有wakeup_source當前的信息,包括:name,active_count,event_count,wakeup_count,expire_count,active_since,total_time,max_time,last_change,prevent_suspend_time。注:代碼實現在@drivers/base/power/wakeup.c -
從wakeup類下獲取某個ws的信息:
/sys/class/wakeup/wakeup<id>/
wakeup類下匯總了所有已注冊的ws,該節點下存在屬性:name, active_count, event_count, wakeup_count,expire_count, active_time_ms, total_time_ms, max_time_ms, last_change_ms, prevent_suspend_time_ms。注:代碼實現在@drivers/base/power/wakeup_stats.c -
從device節點下獲取該設備的ws信息:
/sys/devices/<dev_name>/power/
該節點存在如下屬性信息:wakeup(是否支持喚醒),wakeup_count, wakeup_active_count, wakeup_abort_count, wakeup_expire_count, wakeup_active, wakeup_total_time_ms, max_time_ms, last_time_ms, prevent_sleep_time_ms。注:代碼實現在@drivers/base/power/sysfs.c
注:本文是基于內核kernel-5.10展開。上述分析基于32位系統,若是64位系統,則combined_event_count會被拆分成2個32位分別來紀錄喚醒事件的總數和正在處理中的喚醒事件的總數
文章轉載自:Jayfan_Ma
原文鏈接:https://www.cnblogs.com/jiafan-ma/p/18200874
體驗地址:引邁 - JNPF快速開發平臺_低代碼開發平臺_零代碼開發平臺_流程設計器_表單引擎_工作流引擎_軟件架構