Linux的系统suspend和resume
參考:
www.wowotech.net/linux_kenrel/suspend_and_resume.html
www.wowotech.net/linux_kenrel/pm_interface.html
一、基本介紹
1.Window下的睡眠就是Suspend to RAM, 休眠就是Suspend to Disk,Ubuntu中Suspend就是Stand by(沒有實現Suspend to RAM),Hibernate就是Suspend to Disk。
2.設備驅動若是關注睡眠和喚醒功能就要實現suspend和resume函數,是整個系統的睡眠,電源管理,而不是單獨的某個設備的。
3.Linux系統Suspend實現:
cat/sys/power/state 打印支持的電源管理方式,echo 的時候會讓內核進入某中休眠模式, eg:echo mem > /sys/power/state 對應的內核函數在/sys/power/state中的讀寫方法在kernel/power/main.c中.
power_attr(state);展開:
static struct kobj_attribute state_attr = {
.attr = {
.name = "state",
.mode = 0644,
},
.show = state_show,
.store = state_store,
}
struct kobj_attribute state_attr --> struct attribute * g[] --> struct attribute_group attr_group --> pm_init()
power_kobj = kobject_create_and_add("power", NULL); //在/sys目錄下創建了一個power目錄
sysfs_create_group(power_kobj, &attr_group); //在power目錄中創建了這個state文件
二、suspend/resume流程分析
//includelinuxsuspend.h
typedef int __bitwise suspend_state_t;
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_TO_IDLE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_TO_IDLE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
enum suspend_stat_step {
SUSPEND_FREEZE = 1,
SUSPEND_PREPARE,
SUSPEND_SUSPEND,
SUSPEND_SUSPEND_LATE,
SUSPEND_SUSPEND_NOIRQ,
SUSPEND_RESUME_NOIRQ,
SUSPEND_RESUME_EARLY,
SUSPEND_RESUME
};
//kernel/power/suspend.c
const char * const pm_labels[] = {
[PM_SUSPEND_TO_IDLE] = "freeze", //.[1]="freeze"
[PM_SUSPEND_STANDBY] = "standby",//.[2]="standby"
[PM_SUSPEND_MEM] = "mem",//.[3]="mem"
};
const char *pm_states[PM_SUSPEND_MAX];
void __init pm_states_init(void)
{
/* "mem" and "freeze" are always present in /sys/power/state. */
pm_states[PM_SUSPEND_MEM] = pm_labels[PM_SUSPEND_MEM]; /*.[3]="mem"*/
pm_states[PM_SUSPEND_TO_IDLE] = pm_labels[PM_SUSPEND_TO_IDLE];/*.[1]="freeze"*/
/*
* Suspend-to-idle should be supported even without any suspend_ops,
* initialize mem_sleep_states[] accordingly here.
*/
mem_sleep_states[PM_SUSPEND_TO_IDLE] = mem_sleep_labels[PM_SUSPEND_TO_IDLE]; /*.[1]="s2idle"*/
}
static const char * const mem_sleep_labels[] = {
[PM_SUSPEND_TO_IDLE] = "s2idle", /*.[1]="s2idle"*/
[PM_SUSPEND_STANDBY] = "shallow",/*.[2]="s2idle"*/
[PM_SUSPEND_MEM] = "deep",/*.[3]="mem"*/
};
const char *mem_sleep_states[PM_SUSPEND_MAX];//4
suspend_state_t mem_sleep_current = PM_SUSPEND_TO_IDLE; //1
suspend_state_t mem_sleep_default = PM_SUSPEND_MAX;//4
state_store //(kernel/power/main.c)
pm_suspend //(kernel/power/suspend.c)
enter_state //(kernel/power/suspend.c)
valid_state //(kernel/power/suspend.c) 檢查單板是否支持電源管理,就是全局suspend_ops有沒有被賦值,并調用其suspend_ops->valid()
suspend_prepare //(kernel/power/suspend.c)
pm_prepare_console //(kernel/power/console.c)
pm_notifier_call_chain(PM_SUSPEND_PREPARE) //(kernel/power/main.c) 通知所有關心這個消息的驅動程序!依次調用靜態全局鏈表pm_chain_head中的每一個函數
suspend_freeze_processes //(kernel/power/power.h) 凍結App和內核線程
suspend_devices_and_enter //(kernel/power/suspend.c) 讓設備進入suspend狀態
suspend_ops->begin 如果平臺相關的代碼有begin函數就去調用它,例如Renesas的平臺進入suspend時需要一些預先準備工作,就可以實現這個begin函數
suspend_console //(kernel/printk/printk.c) 串口suspend狀態,此時串口就用不了了
dpm_suspend_start //(drivers/base/power/main.c)
dpm_prepare //(drivers/base/power/main.c)
對全局鏈表dpm_list(drivers/base/power/power.c)中的每一個設備都調用其prepare函數,在這里面可以做一些準備工作
dev->pm_domain->ops.prepare 或 [struct dev_pm_ops ops]
dev->type->pm->prepare 或 [struct dev_pm_ops *pm]
dev->class->pm->prepare 或 [struct dev_pm_ops *pm]
dev->bus->pm->prepare 或 [struct dev_pm_ops *pm]
dev->driver->pm->prepare [struct dev_pm_ops *pm] [struct device_driver中的在這,優先級最低]
dpm_suspend //(drivers/base/power/main.c)
對全局鏈表dpm_prepared_list中的每一個設備都調用device_suspend()
device_suspend //(drivers/base/power/main.c)
dpm_wait_for_children //(drivers/base/power/main.c) 等待其每一個孩子進入suspend狀態
dev->pm_domain->ops->suspend 或
dev->type->pm->suspend 或
dev->class->pm->suspend 或
dev->bus->pm->suspend 或
因此自己在寫驅動的時候可以在其pm_domain中或type->pm中或class->pm中或bus->pm中加入suspend函數
suspend_enter //(kernel/power/suspend.c) 設備都進入suspend狀態了接下來就是CPU了
suspend_ops->prepare 單板的prepare函數若存在就調用
dpm_suspend_end //(drivers/base/power/main.c)
dpm_suspend_late //(drivers/base/power/main.c) 對全局靜態鏈表dpm_suspended_list中的每一個條目都調用device_suspend_late()
device_suspend_late() //(drivers/base/power/main.c) 調用此設備的
dev->pm_domain->ops->suspend_late 或
dev->type->pm->suspend_late 或
dev->class->pm->suspend_late 或
dev->bus->pm->suspend_late 或
dev->driver->pm->suspend_late 或
suspend_ops->prepare_late 調用單板相關的函數,可以做一些清理,若單板不需要也可以不實現它
disable_nonboot_cpus //(kernel/cpu.c) 多核Soc中非用于啟動內核的CPU叫做nonboot_cpu,停止non-boot CPU
arch_suspend_disable_irqs //(include/linux/suspend.h)//關閉中斷,extern的,Renesas上沒有實現
syscore_suspend 關閉核心模塊
suspend_ops->enter 調用單板相關的函數,這里真正進入suspend狀態了,如三星的是s3c_pm_enter(),里面通過any_allowed()檢測有沒有設置喚醒源
若沒有設置是不允許睡眠的。這個函數下面單獨列出
===================================上面是休眠,下面就是喚醒操作了==================================
當我們按下某個按鍵并且這個按鍵是喚醒源的話,就會喚醒CPU,從Uboot開始執行
按鍵喚醒源 --> Uboot --> 讀寄存器GSTATUS3 --> 就會執行s3c_cpu_resume()
syscore_resume //(drivers/base/syscore.c) 對全局鏈表syscore_ops_list中的每一個node都調用其resume()
arch_suspend_enable_irqs //(include/linux/suspend.h)
enable_nonboot_cpus //(kernel/cpu.c)
suspend_ops->wake 如果單板有對應的wake()就調用
dpm_resume_start(PMSG_RESUME) //(drivers/base/power/main.c)
dpm_resume_noirq(state); //(drivers/base/power/main.c) 對全局鏈表dpm_noirq_list中的每一個設備都執行device_resume_noirq
device_resume_noirq //(drivers/base/power/main.c) 對每一個設備都調用
dev->pm_domain->ops->resume_noirq 或
dev->type->pm->resume_noirq 或
dev->class->pm->resume_noirq 或
dev->bus->pm->resume_noirq 或
dev->driver->pm->resume_noirq 或
執行完resume_noirq的所有設備都會被放在全局鏈表dpm_late_early_list中
resume_device_irqs //(kernel/irq/pm.c)
resume_irqs //(kernel/irq/pm.c)
__enable_irq //(kernel/irq/pm.c) 對全局數組irq_desc中的每一個irq都調用__enable_irq,但是Renesas的BSP沒有實現,里面還有一個野指針
dpm_resume_early(state); //(drivers/base/power/main.c) 對全局鏈表dpm_late_early_list中的每一個元素都執行device_resume_early
device_resume_early //(drivers/base/power/main.c)
dev->pm_domain->ops->resume_early 或
dev->type->pm->resume_early 或
dev->class->pm->resume_early 或
dev->bus->pm->resume_early 或
dev->driver->pm->resume_early 或
suspend_ops->finish() 如果單板有對應的finish()就調用,三星的對應下面
suspend_test_start //(kernel/power/suspend_test.c)
dpm_resume_end(PMSG_RESUME); //(drivers/base/power/main.c)
dpm_resume //(drivers/base/power/main.c) 對全局鏈表dpm_suspended_list中的每一個dev都調用device_resume()
device_resume //(drivers/base/power/main.c)
dev->pm_domain->ops->resume 或
dev->type->pm->resume 或
dev->class->pm->resume 或
dev->bus->pm->resume 或
dev->driver->pm->resume 或
然后將所有的設備移動到全局鏈表dpm_prepared_list中
dpm_complete //(drivers/base/power/main.c) 對全局鏈表dpm_prepared_list中的每一個設備都調用device_complete()
device_complete //(drivers/base/power/main.c)
dev->pm_domain->ops.complete 或
dev->type->pm.complete 或
dev->class->pm.complete 或
dev->bus->pm.complete 或
dev->driver->pm.complete 或
suspend_test_finish //(kernel/power/suspend_test.c) 打印一些log出來
resume_console //(kernel/printk.c)
console_unlock //(kernel/printk.c)
call_console_drivers //(kernel/printk.c) 關閉本地中斷獲取spin鎖后調用控制臺打印函數以poll方式打印內核log
suspend_ops->end() 如果單板有對應的end()就調用
suspend_ops->recover() 如果dpm_suspend_start失敗或者suspend_test失敗,單板有對應的recover()就調用
suspend_finish //(kernel/power/suspend.c)
suspend_thaw_processes(); //(kernel/power/power.h)喚醒應用程序
pm_notifier_call_chain(PM_POST_SUSPEND); //(kernel/power/main.c) 通知關注這個事件的App程序,對全局pm_chain_head->head中的每一個都調用其notifier_call()
pm_restore_console(); //(kernel/power/console.c)
返回用戶空間
s3c_pm_enter 展開:
any_allowed 檢測有沒有設置喚醒源
samsung_pm_save_gpios(); 保存一些狀態,gpio uart狀態
samsung_pm_saved_gpios();
s3c_pm_save_uarts();
s3c_pm_save_core();
s3c_pm_configure_extint 配置一下喚醒源
pm_cpu_prep s3c2410_pm_add()中對這個函數指針賦值
s3c2410_pm_prepare 這個函數中將s3c_cpu_resume()這個函數的物理地址寫入了GSTATUS3寄存器中
GSTATUS3 = s3c_cpu_resume() 當系統被喚醒的時候會去執行Uboot,Uboot中會去讀GSTATUS3得到一個函數的地址然后去執行它
s3c_pm_arch_stop_clocks 關閉時鐘
cpu_suspend(0, pm_cpu_sleep) 這個是最重要的suspend函數了,參數2 pm_cpu_sleep 作為回調函數
__cpu_suspend(arg, pm_cpu_sleep) arch/arm/kernel/sleep.S 中是個匯編代碼,pm_cpu_sleep作為第二個參數傳入,就保存在r1里面
stmfd sp!, {r0, r1} @ save suspend func arg and pointer
ldmfd sp!, {r0, pc} @ call suspend fn 恢復的時候把r1里面的值恢復到pc指針中! 相當于執行了pm_cpu_sleep
pm_cpu_sleep = ENTRY(s3c2410_cpu_suspend) /arch/arm/mach-s3c24xx/s3c2410.S
先讀一遍這幾個寄存器,讀的原因:休眠的時候通過寫這個REFRESH寄存器把SDRAM給關掉,關閉掉后還要通過寫寄存器CLKCON把
CPU給停掉,但是當內核去訪問這些寄存器的時候,內核需要使用虛擬地址,就要使用到SDRAM上的頁表,但是現在已經要先關閉SDRAM了,
就不能再訪問SDRAM上的頁表了,因此需要先讀取一下那些寄存器,緩存這些寄存器的頁表到TLB里面,之后通過TLB得到翻譯后的這些寄存
器的物理地址。
ldr r4, =S3C2410_REFRESH
ldr r5, =S3C24XX_MISCCR
ldr r6, =S3C2410_CLKCON
下面先去dummy執行一遍,pc指針肯定也不等于0,目前先把這些指令緩存在I cache里面。
teq pc, #0 @ first as a trial-run to load cache
bl s3c2410_do_sleep
這里就可以從Icache中獲取這些指令,執行SDRAM的關閉和CPU的睡眠操作了
s3c2410_do_sleep:
streq r7, [ r4 ] @ SDRAM sleep command
streq r8, [ r5 ] @ SDRAM power-down config
streq r9, [ r6 ] @ CPU sleep
========================上面是休眠,下面就是喚醒操作了===========================
s3c_pm_restore_core();
s3c_pm_restore_uarts();
samsung_pm_restore_gpios();
s3c_pm_restored_gpios();
流程分析結論:
1.PM Core會依次調用“prepare-->suspend-->suspend_late-->suspend_noirq-------wakeup--------->resume_noirq-->resume_early-->resume-->complete”
總體流程就是先讓你準備一下,然后再讓你休眠,休眠和喚醒是反著來的。在suspend之前可以做一些準備工作,suspend之后可以做一些清理函數,若是認
2.為沒有什么好準備或清理的,設備驅動中可以不實現prepare函數和suspend_late函數,只實現suspend函數。
3.可以參考函數dpm_show_time()里面的實現來在內核中打印時間
struct device *dev;
list_for_each_entry(dev, &dpm_suspended_list, power.entry)
4.list_for_each_entry:dummy dev的power.entry實體掛載在鏈表dpm_suspended_list上,返回dev實體
三、查看三星單板suspend功能實現
/arch/arm/plat-samsung/pm.c 中的struct platform_suspend_ops s3c_pm_ops
s3c_pm_init
suspend_set_ops(&s3c_pm_ops);
設置喚醒源:配置GPIO引腳工作于中斷模式,設置它的觸發方式
s3c_pm_enter --> s3c_pm_configure_extint()
使用哪個交叉編譯工具鏈只需要在PATH中設置其路徑即可!有多個不同版本的話設置一個自己想要的版本的路徑到PATH中,就選用了這個交叉工具鏈。
是make uImage
修改按鍵驅動在request_irq()之后調用irq_set_irq_wake()來指定此中斷為喚醒源(沒有喚醒源是不允許進入睡眠模式的)
四、修改驅動支持電源管理
a.通知notifier
①由上流程分析可知在凍結App之前,使用pm_notifier_call_chain(PM_SUSPEND_PREPARE)來通知應用程序
②由上流程分析可知在重啟App之后,使用pm_notifier_call_chain(PM_POST_SUSPEND);
如果驅動在凍結App之前之后有些事情要做,可以使用register_pm_notifier()注冊這兩個notifier
一般驅動進入休眠是在凍結進程之后進行休眠的,以免驅動休眠之后還有App訪問到它!
b.添加suspend,resume函數,可以參考s3c2410fb.c
1.若在struct platform_driver里面加的話只有suspend和resume兩個選項,但是這里是內核即將遺棄的,平臺(一般)設備沒有pm_domain、type、class,但是有總線bus
static struct platform_driver s3c2412fb_driver = {
.probe = s3c2412fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = { /*若此內部有.pm域,優先調用driver.pm,而不是platform_driver.suspend*/
.name = "s3c2412-lcd",
},
};
平臺驅動struct platform_driver中注冊了suspend,resume函數何時被調用:
dev->pm_domain->ops.suspend
dev->type->pm->suspend
dev->class->pm->suspend
dev->bus->pm->suspend
dev->driver->pm->suspend
__platform_driver_register()中drv->driver.bus = &platform_bus_type;[全局]
由上面的調用邏輯dev->bus->pm->suspend會被調用,而platform_bus_type;[全局]有.pm = &platform_dev_pm_ops,
platform_pm_suspend() --> platform_legacy_suspend() --> platform_driver.suspend.
2.添加suspend,resume函數
老方法:在platform_driver中實現suspend/resume方法
新方法:在platform_driver.driver.pm中實現suspend/resume方法,[可參考ac97c.c]
3.實驗現象
實現中休眠之前LCD上的圖像在休眠后LCD上的圖像不見了,因為在喚醒時
LCD做串口控制臺被清理了,在menuconfig中將<*> Framebuffer Console support去掉即可!讓LCD不做控制臺
五、代碼附錄
1.notifier
static int lcd_suspend_notifier(struct notifier_block *nb, unsigned long event, void *dummy)
{
switch (event) {
case PM_SUSPEND_PREPARE:
printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE
");
return NOTIFY_OK;
case PM_POST_SUSPEND:
printk("lcd suspend notifiler test: PM_POST_SUSPEND
");
return NOTIFY_OK;
default:
return NOTIFY_DONE;
}
}
static struct notifier_block lcd_pm_notif_block = {
.notifier_call = lcd_suspend_notifier,
};
static int lcd_init(void)
{
......
register_pm_notifier(&lcd_pm_notif_block); //由上流程可知在pm_notifier_call_chain時會傳入不同的參數調用這個函數。
......
return 0;
}
static void lcd_exit(void)
{
......
unregister_pm_notifier(&lcd_pm_notif_block);
......
}
module_init(lcd_init);
module_exit(lcd_exit);
2.suspend/resume實現
static int lcd_suspend(struct device *dev)
{
/*1.保存LCD相關寄存器狀態*/
/*2.關閉時鐘,斷電進入suspend狀態*/
return 0;
}
static int lcd_resume(struct device *dev)
{
/*1.還原LCD相關寄存器狀態*/
/*2.使能時鐘,上電進入suspend狀態*/
return 0;
}
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
};
struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
static int lcd_init(void)
{
register_pm_notifier(&lcd_pm_notif_block);
platform_driver_register(&lcd_drv);
return 0;
}
static void lcd_exit(void)
{
unregister_pm_notifier(&lcd_pm_notif_block);
platform_device_unregister(&lcd_dev);
platform_driver_unregister(&lcd_drv);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
總結
以上是生活随笔為你收集整理的Linux的系统suspend和resume的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue offsetTop顶部距离
- 下一篇: Excel技巧—几个快速填充公式更高效的