轉自http://www.linuxidc.com/Linux/2011-07/39477p3.htm
1.用戶空間的接口
在kernel/power/main.c中,定義了一組sysfs的屬性文件,其中一個定義是:
power_attr(state);
把這個宏展開後:
staticstruct kobj_attribute state_attr = { \
.attr ={ \
.name = "state", \
.mode = 0644, \
}, \
.show =state_show, \
.store =state_store, \
}
我們再看看main.c的入口:
staticint __init pm_init(
void)
{
......
power_kobj =kobject_create_and_add("power", NULL);
if (!power_kobj)
return -ENOMEM;
return sysfs_create_group(power_kobj,&attr_group);
}
顯然,該函數執行後,會在生成/sys/power目錄,該目錄下會建立一系列屬性文件,其中一個就是/sys/power/state文件。用戶空間向該文件的寫入將會導致state_store被調用,讀取該文件將會導致state_store函數被調用。
現在回到Android的HAL層中,查看一下代碼:hardware/libhardware_legacy/power/power.c:
//定義寫入/sys/power/state的命令字符串
staticconst
char *off_state = "mem";
staticconst
char *on_state = "on";
//打開/sys/power/state等屬性文件,保存相應的文件描述符
staticint
open_file_descriptors(constchar *
const paths[])
{
int i;
for (i=0; i<OUR_FD_COUNT; i++) {
int fd = open(paths[i], O_RDWR);
if (fd < 0) {
fprintf(stderr, "fatal erroropening \"%s\"\n", paths[i]);
g_error = errno;
return -1;
}
g_fds[i] = fd;
}
g_error = 0;
return 0;
}
最終,用戶空間的電源管理系統會調用set_screen_state函數來觸發suspend的流程,該函數實際上就是往/sys/power/state文件寫入"mem"或"on"命令字符串。
int set_screen_state(inton)
{
......
initialize_fds();
......
char buf[32];
int len;
if(on)
len = snprintf(buf,
sizeof(buf),"%s", on_state);
else len = snprintf(buf,
sizeof(buf),"%s", off_state);
buf[
sizeof(buf) - 1] = '\0';
len = write(g_fds[REQUEST_STATE], buf,len);
......
return 0;
}
2.內核中數據結構和接口
與earlysuspend相關的數據結構和接口都在earlysuspend.h中進行了定義:
struct early_suspend {
#ifdef CONFIG_HAS_EARLYSUSPEND
structlist_head link;
int level;
void(*suspend)(
struct early_suspend *h);
void(*resume)(
struct early_suspend *h);
#endif
};
希望執行early suspend的設備,他的設備驅動程序需要向電源管理系統注冊,該結構體用於向電源管理系統注冊earlysuspend/lateresume,當電源管理系統啟動suspend流程時,回調函數suspend會被調用,相反,resume的最後階段,回調函數resume會被調用,level字段用於調整該結構體在注冊鏈表中的位置,suspend時,level的數值越小,回調函數的被調用的時間越早,resume時則反過來。Android預先定義了3個level等級:
enum {
EARLY_SUSPEND_LEVEL_BLANK_SCREEN = 50,
EARLY_SUSPEND_LEVEL_STOP_DRAWING = 100,
EARLY_SUSPEND_LEVEL_DISABLE_FB = 150,
};
如果你想你的設備在FB設備被禁止之前執行他的early suspend回調,設備驅動程序應該把level值設定為小於150的某個數值,然後向系統注冊early_suspend結構。注冊和反注冊函數是:
void register_early_suspend(struct early_suspend *handler);
void unregister_early_suspend(struct early_suspend *handler);
所有注冊到系統中的early_suspend結構都會按level值按順序加入到全局鏈表early_suspend_handlers中。
3. 工作流程
首先,我們從kernel/power/wakelock.c中的初始化函數開始:
static int __init wakelocks_init(
void)
{
int ret;
int i;
......
for (i = 0; i < ARRAY_SIZE(active_wake_locks); i++)
INIT_LIST_HEAD(&active_wake_locks[i]);
......
wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "main");
wake_lock(&main_wake_lock);
wake_lock_init(&unknown_wakeup, WAKE_LOCK_SUSPEND, "unknown_wakeups");
......
ret = platform_device_register(&power_device);
ret = platform_driver_register(&power_driver);
......
suspend_work_queue = create_singlethread_workqueue("suspend");
......
return 0;
}
可以看到,顯示初始化active_wake_locks鏈表數組,然後初始化並且鎖住main_wake_lock,注冊平台設備power_device,這些數組、鎖和power_device我們在後續文章在討論,這裡我們關注的最後一個動作:創建了一個工作隊列線程suspend_work_queue,該工作隊列是earlysuspend的核心所在。
系統啟動完成後,相關的驅動程序通過register_early_suspend()函數注冊了early suspend特性,等待一段時間後,如果沒有用戶活動(例如按鍵、觸控等操作),用戶空間的電源管理服務最終會調用第一節提到的set_screen_state()函數,透過sysfs,進而會調用到內核中的state_store():
static ssize_t state_store(
struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf,
size_t n)
{
#ifdef CONFIG_SUSPEND
#ifdef CONFIG_EARLYSUSPEND
suspend_state_t state = PM_SUSPEND_ON;
#else
suspend_state_t state = PM_SUSPEND_STANDBY;
#endif
const char *
const *s;
#endif
char *p;
int len;
int error = -EINVAL;
p = memchr(buf, '\n', n);
len = p ? p - buf : n;
/* First, check if we are requested to hibernate */
if (len == 4 && !strncmp(buf, "disk", len)) {
error = hibernate();
goto Exit;
}
#ifdef CONFIG_SUSPEND
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
break;
}
if (state < PM_SUSPEND_MAX && *s)
#ifdef CONFIG_EARLYSUSPEND
if (state == PM_SUSPEND_ON || valid_state(state)) {
error = 0;
request_suspend_state(state);
}
#else
error = enter_state(state);
#endif
#endif
Exit:
return error ? error : n;
}
看到了沒,前一篇文章http://www.linuxidc.com/Linux/2011-07/39478.htm說過,suspend to disk做了特殊處理,這裡直接比較傳入的字符串,而不是使用後續的pm_states數組,這裡我不關心suspendto disk,所以略過hibernate的分析。
緊接著,通過pm_states數組,根據命令字符串查詢得到請求的狀態,默認情況下,Android的內核都會配置了CONFIG_EARLYSUSPEND,所以會調用request_suspend_state()函數,不過在調用該函數之前會先valid_state()一下,這給了平台相關的代碼一個機會確認該平台是否支持所請求的電源狀態。
void request_suspend_state(suspend_state_t new_state)
{
unsigned
long irqflags;
int old_sleep;
spin_lock_irqsave(&state_lock, irqflags);
old_sleep = state & SUSPEND_REQUESTED;
......
if (!old_sleep && new_state != PM_SUSPEND_ON) {
state |= SUSPEND_REQUESTED;
if (queue_work(suspend_work_queue, &early_suspend_work))
pr_info("early_suspend_work is in queue already\n");
}
else if (old_sleep && new_state == PM_SUSPEND_ON) {
state &= ~SUSPEND_REQUESTED;
wake_lock(&main_wake_lock);
if (!queue_work(suspend_work_queue,&late_resume_work))
pr_info("late_resume_work is in queue already\n");
}
requested_suspend_state = new_state;
spin_unlock_irqrestore(&state_lock, irqflags);
}
還記得前面初始化時建立的工作隊列suspend_woek_queue嗎?根據之前的電源狀態和請求的狀態, request_suspend_state()只是簡單地向suspend_work_queue中加入early_suspend_work或者是late_resume_work並調度他們執行。early_suspend_work的工作函數是early_suspend():
staticvoid early_suspend(struct work_struct *work)
{
struct early_suspend *pos;
unsigned long irqflags;
int abort = 0;
mutex_lock(&early_suspend_lock);
spin_lock_irqsave(&state_lock,irqflags);
if (state == SUSPEND_REQUESTED)
state |= SUSPENDED;
else
abort = 1;
spin_unlock_irqrestore(&state_lock,irqflags);
if (abort) {
......
}
......
list_for_each_entry(pos,&early_suspend_handlers, link) {
if (pos->suspend != NULL) {
if (debug_mask &DEBUG_SUSPEND)
printk(KERN_DEBUG"pos->suspend: %pF begin\n", pos->suspend);
pos->suspend(pos);
if (debug_mask &DEBUG_SUSPEND)
printk(KERN_DEBUG"pos->suspend: %pF finish\n", pos->suspend);
}
}
mutex_unlock(&early_suspend_lock);
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend:sync\n");
sys_sync();
abort:
spin_lock_irqsave(&state_lock,irqflags);
if (state ==SUSPEND_REQUESTED_AND_SUSPENDED)
wake_unlock(&main_wake_lock);
spin_unlock_irqrestore(&state_lock,irqflags);
}
終於看到啦,early_suspend()遍歷early_suspend_handlers鏈表,從中取出各個驅動程序注冊的early_suspend結構,然後調用它的suspend回調函數。最後,釋放main_wake_lock鎖,至此整個earlysuspend的流程完成。下面的序列圖清晰地表明了整個調用的過程:
圖3.1 early suspend調用流程
但是,這時整個系統只是出於所謂的idle狀態,cpu還在工作,後台進程也在工作中,那什麼時候系統會真正地進入睡眠狀態?注意到最後一句關鍵的調用了沒有:
wake_unlock(&main_wake_lock);
解鎖動作會觸發標准linux的suspend流程,這個過程就留給寫一篇文章討論吧。