五、suspend和resume代碼走讀
下面對suspend分的幾個階段都是按照pm test的5中模式來劃分的:freezer、devices、platform、processors、core。
suspend第一階段:freezer
int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex)) // def in kernel/kernel/power/main.c
return -EBUSY;
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done./n"); // 同步文件系統
pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]);
error = suspend_prepare();
// suspend前準備工作:Run suspend notifiers, allocate a console and stop all processes
if (error) // 如果一些準備工作失敗,通常爲凍結進程的時候某些進程拒絕進入凍結模式
goto Unlock; // 釋放鎖,然後退出
if (suspend_test(TEST_FREEZER))
// 檢查上層下達的命令是否是:
// echo freezer > /sys/power/pm_test
// echo mem > /sys/power/state
// 是的話,延時5s後,然後做一些解凍進程等工作就返回
goto Finish;
pr_debug("PM: Entering %s sleep/n", pm_states[state]);
error = suspend_devices_and_enter(state); // 休眠外設
Finish:
pr_debug("PM: Finishing wakeup./n");
suspend_finish(); // 解凍進程,發廣播通知等
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter) // mtk_pm_enter() in mtkpm.c
return -EPERM;
pm_prepare_console(); //給suspend分配一個虛擬終端來輸出信息
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
// 廣播一個通知給通知鏈pm_chain_head,該通知鏈上都是用函數register_pm_notifier()註冊的pm通知項(也可以用宏pm_notifier定義和註冊),這裏按照註冊時候的定義的優先級來調用該通知鏈上註冊的回調函數。
// PM_SUSPEND_PREPARE是事件值,表示將要進入suspend
if (error)
goto Finish;
error = usermodehelper_disable(); // 關閉用戶態的helper進程
if (error)
goto Finish;
error = suspend_freeze_processes();
// 凍結所有的進程, 這裏會保存所有進程當前的狀態。
if (!error)
return 0;
// 也許有一些進程會拒絕進入凍結狀態, 當有這樣的進程存在的時候, 會導致凍結失敗,此函數就會放棄凍結進程,並且解凍剛纔凍結的所有進程.
suspend_thaw_processes(); // 進程解凍
usermodehelper_enable(); // enable helper process
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND); // 廣播退出suspend的通知
pm_restore_console();
return error;
}
// 如果不支持pm debug的話,該函數直接返回0
static int suspend_test(int level)
{
#ifdef CONFIG_PM_DEBUG
if (pm_test_level == level) {
printk(KERN_INFO "suspend debug: Waiting for 5 seconds./n");
mdelay(5000);
return 1;
}
#endif /* !CONFIG_PM_DEBUG */
return 0;
}
static void suspend_finish(void)
{
suspend_thaw_processes();
usermodehelper_enable();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
}
同步文件系統函數sys_sync()調用後,將會執行函數suspend_prepare()來做一些進入suspend之前的準備工作:Run suspend notifiers, allocate a console and stop all processes,disable user mode hleper process。之後將會調用suspend_test(TEST_FREEZER)來判斷上層是否有下這樣的命令下來:echo freezer > /sys/power/pm_test(如果pm debug支持),如果沒有下這個命令或者系統根本就不支持pm debug,那麼直接返回0。如果該測試函數返回了1,那麼就不會繼續往下走suspend的流程了,而是調用函數suspend_finish()來結束freezer模式的pm test。返回0,將會進入第二階段繼續suspend的流程。
suspend第二階段:devices
後續所有對suspend劃分的階段都包含在函數suspend_devices_and_enter(state)之中,所以這個函數是關鍵所在。
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
if (!suspend_ops)
// 一個重要的函數指針結構體,特定平臺的不一樣,
// kernel/arch/arm/mach-mt6516/mtkpm.c
return -ENOSYS;
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
// 調用特定平臺實現的suspend_begin函數,
// 這裏沒做什麼實際的工作,打印了點字符串
if (error)
goto Close; // 如果有錯,執行特定平臺的suspend_end函數
}
suspend_console(); // suspend console subsystem
suspend_test_start(); // suspend devices超時警告測試
error = dpm_suspend_start(PMSG_SUSPEND); // suspend all devices, 外設休眠
// 關鍵函數之一, kernel/drivers/base/power/main.c
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend/n");
goto Recover_platform; // 如果外設休眠過程出現錯誤,將會終止suspend過程,直接跳到某標籤出resume外設
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES)) // suspend第二階段以此爲界
goto Recover_platform;
suspend_enter(state); // 關鍵函數之一, pm test的後三種模式都在該函數中進行測試:platform、cpus、core
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover(); // 該函數目前的平臺沒有實現
goto Resume_devices;
}
@kernel/drivers/base/power/main.c
int dpm_suspend_start(pm_message_t state)
{
int error;
might_sleep();
error = dpm_prepare(state);
if (!error)
error = dpm_suspend(state); // 這兩個函數的架構有一些類似
return error;
這兩個函數如果在執行某一個device的->prepare()和->suspend回調函數的時候出錯,都將會直接跳出返回錯誤碼,不理會後續devices。
}
static int dpm_prepare(pm_message_t state)
{
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx); // 鎖住dpm_list鏈表
transition_started = true; // device_pm_add()中使用
while (!list_empty(&dpm_list)) { // dpm_list上掛着所有的devices
// 關於device中的這部分內容,參考文檔:
// 新版linux系統設備架構中關於電源管理方式的變更.txt
struct device *dev = to_device(dpm_list.next); // 取得對應的device結構體
get_device(dev);
dev->power.status = DPM_PREPARING;
// 將該設備的電源狀態設置成DPM_PREPARING,表示該設備正準備着進入相應的省電模式,這之後就會調用和該設備有關的所以的-->prepare函數
mutex_unlock(&dpm_list_mtx);
pm_runtime_get_noresume(dev);
// 電源管理新方式中比較複雜的用法,這裏沒有使用到,所以直接跳過
if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {
/* Wake-up requested during system sleep transition. */
pm_runtime_put_noidle(dev);
error = -EBUSY;
} else {
error = device_prepare(dev, state);
// 這個纔是真正執行-->prepare回調函數的地方
}
mutex_lock(&dpm_list_mtx);
if (error) {
dev->power.status = DPM_ON;
if (error == -EAGAIN) { // try again
put_device(dev);
error = 0;
continue;
}
printk(KERN_ERR "PM: Failed to prepare device %s "
"for power transition: error %d/n",
kobject_name(&dev->kobj), error);
put_device(dev);
break;
}
dev->power.status = DPM_SUSPENDING;
// 這之後就不能以它爲父設備再註冊設備了,可以從函數device_pm_add
// 中看出來
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &list);
// 爲了該函數中循環鏈表之用, list_empty(&dpm_list)
put_device(dev);
}
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
return error;
}
從這個函數我們可以發現,每一個device的dev->power.status狀態都會有如下軌跡的變化:DPM_ON(device剛剛註冊完的初始狀態) --> DPM_PREPARING --> DPM_SUSPENDING --> 未完待續...
static int device_prepare(struct device *dev, pm_message_t state)
{
int error = 0;
down(&dev->sem);
// dev->bus絕大多數都存在,dev->bus->pm這個就不一定了,不過platform bus一定存在,而i2c bus就沒有,dev->bus->pm->prepare這個函數如果存在就會被後面給調用到,以platform bus爲例,該函數的實現完全回去調用device對應的driver->pm->prepare()函數(如果存在的話),當然driver->pm->prepare()不存在就什麼也不做了 。
if (dev->bus && dev->bus->pm && dev->bus->pm->prepare) {
pm_dev_dbg(dev, state, "preparing ");
error = dev->bus->pm->prepare(dev);
suspend_report_result(dev->bus->pm->prepare, error);
if (error)
goto End;
}
// dev->type這個很多設備都有,但是dev->type->pm這個卻很少有了
if (dev->type && dev->type->pm && dev->type->pm->prepare) {
pm_dev_dbg(dev, state, "preparing type ");
error = dev->type->pm->prepare(dev);
suspend_report_result(dev->type->pm->prepare, error);
if (error)
goto End;
}
// dev->class很少有設備有
if (dev->class && dev->class->pm && dev->class->pm->prepare) {
pm_dev_dbg(dev, state, "preparing class ");
error = dev->class->pm->prepare(dev);
suspend_report_result(dev->class->pm->prepare, error);
}
End:
up(&dev->sem);
return error;
}
static int dpm_suspend(pm_message_t state)
{
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
dpm_drv_wdset(dev);
error = device_suspend(dev, state);
dpm_drv_wdclr(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
put_device(dev);
break;
// 如果某個dev的suspend出錯了,那麼將不會執行後續dev的suspend函數,將會直接返回錯誤碼給上級函數。
}
dev->power.status = DPM_OFF;
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &list);
put_device(dev);
}
device_suspend_index = 0; // device_suspend()函數中使用
list_splice(&list, dpm_list.prev);
mutex_unlock(&dpm_list_mtx);
return error;
}
前面執行了->prepare回調函數之後,dev->power.status狀態已經爲DPM_SUSPENDING了,那麼在這裏執行了->suspend回調函數後,其狀態正常情況下將會變成DPM_OFF --> 未完待續...
static int device_suspend(struct device *dev, pm_message_t state)
{
int error = 0;
static int index = 0;
struct platform_device *pdev = to_platform_device(dev);
down(&dev->sem);
if (dev->class) {
if (dev->class->pm) { // 新式的電源管理方案: dev_pm_ops
pm_dev_dbg(dev, state, "class ");
if (pdev->name)
printk("[%d][0x%x] class
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] class device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->class->pm, state);
// 調用回調函數dev->class->pm->suspend()
if (pdev->name)
printk("[%d][0x%x] class device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] class device_suspend
pass/n/r",device_suspend_index++);
} else if (dev->class->suspend) { // 舊式的電源管理方案
pm_dev_dbg(dev, state, "legacy class ");
if (pdev->name)
printk("[%d][0x%x] legacy class
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] legacy class
device_suspend/n/r",device_suspend_index);
error = dev->class->suspend(dev, state);
if (pdev->name)
printk("[%d][0x%x] legacy class device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] legacy class device_suspend
pass/n/r",device_suspend_index++);
suspend_report_result(dev->class->suspend, error); // 錯誤處理
}
if (error)
goto End;
}
if (dev->type) {
if (dev->type->pm) {
pm_dev_dbg(dev, state, "type ");
if (pdev->name)
printk("[%d][0x%x] type
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] type device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->type->pm, state);
if (pdev->name)
printk("[%d][0x%x] type device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] type device_suspend
pass/n/r",device_suspend_index++);
}
if (error)
goto End;
}
if (dev->bus) {
if (dev->bus->pm) {// platform bus 使用的是這種新式的電源管理方式
pm_dev_dbg(dev, state, "");
if ((u32)pdev->name & 0xC0000000)
printk("[%d][%s] device_suspend/n/r",device_suspend_index,
pdev->name);
else
printk("[%d] device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->bus->pm, state);
// 調用回調函數dev->bus->pm->suspend(),實際上會根據device_driver中pm項是否存在來選擇使用driver的老式suspend還是新式的suspend,這要看對應的driver中怎麼實現的了。
if ((u32)pdev->name & 0xC0000000)
printk("[%d][%s] device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] device_suspend pass/n/r",device_suspend_index++);
} else if (dev->bus->suspend) { // i2c bus就是用的這種老式的方式
pm_dev_dbg(dev, state, "legacy ");
if (pdev->name)
printk("[%d][0x%x] legacy
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] legacy device_suspend/n/r",device_suspend_index);
error = dev->bus->suspend(dev, state);
// 該函數總也是調用的i2c_driver的suspend函數
if (pdev->name)
printk("[%d][0x%x] legacy device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] legacy device_suspend
pass/n/r",device_suspend_index++);
suspend_report_result(dev->bus->suspend, error);
}
}
End:
up(&dev->sem);
return error;
}
linux的電源管理是通過設備樹架構來操作的,也就是說,suspend必須是先操作葉子節點設備,一級一級往上一次操作。標誌linux中設備的分類是很細的,只不過實際使用中並沒有區分開來,上面這個函數就可以看得到設備樹的影子,
bus > device_type > class > device。
第二種pm test模式devices將會在函數suspend_test(TEST_DEVICES)中來確認,如果上層有下達echo devices > /sys/power/pm_test;echo mem > /sys/power/state
那麼這裏將會延時5s,然後返回1給函數suspend_devices_and_enter(),該函數將不 會繼續往下走suspend的流程,而是直接調用函數 dpm_resume_end(PMSG_RESUME)來resume所有外設。正常的suspend模式下是 沒有任何pm test模式出現的,所有這裏考慮正常的suspend流程,繼續往下走。