日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

Linux的系统suspend和resume

發(fā)布時間:2023/12/13 综合教程 29 生活家
生活随笔 收集整理的這篇文章主要介紹了 Linux的系统suspend和resume 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

參考:

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(沒有實現(xiàn)Suspend to RAM),Hibernate就是Suspend to Disk。
2.設(shè)備驅(qū)動若是關(guān)注睡眠和喚醒功能就要實現(xiàn)suspend和resume函數(shù),是整個系統(tǒng)的睡眠,電源管理,而不是單獨的某個設(shè)備的。
3.Linux系統(tǒng)Suspend實現(xiàn):
  cat/sys/power/state 打印支持的電源管理方式,echo 的時候會讓內(nèi)核進入某中休眠模式, eg:echo mem > /sys/power/state 對應(yīng)的內(nèi)核函數(shù)在/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目錄下創(chuàng)建了一個power目錄
sysfs_create_group(power_kobj, &attr_group); //在power目錄中創(chuàng)建了這個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有沒有被賦值,并調(diào)用其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) 通知所有關(guān)心這個消息的驅(qū)動程序!依次調(diào)用靜態(tài)全局鏈表pm_chain_head中的每一個函數(shù)
                suspend_freeze_processes //(kernel/power/power.h) 凍結(jié)App和內(nèi)核線程
            suspend_devices_and_enter //(kernel/power/suspend.c) 讓設(shè)備進入suspend狀態(tài)
                suspend_ops->begin 如果平臺相關(guān)的代碼有begin函數(shù)就去調(diào)用它,例如Renesas的平臺進入suspend時需要一些預(yù)先準備工作,就可以實現(xiàn)這個begin函數(shù)
                suspend_console //(kernel/printk/printk.c) 串口suspend狀態(tài),此時串口就用不了了
                dpm_suspend_start //(drivers/base/power/main.c)
                    dpm_prepare //(drivers/base/power/main.c)
                        對全局鏈表dpm_list(drivers/base/power/power.c)中的每一個設(shè)備都調(diào)用其prepare函數(shù),在這里面可以做一些準備工作
                        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中的在這,優(yōu)先級最低]
                    dpm_suspend //(drivers/base/power/main.c) 
                        對全局鏈表dpm_prepared_list中的每一個設(shè)備都調(diào)用device_suspend()
                        device_suspend //(drivers/base/power/main.c)
                            dpm_wait_for_children //(drivers/base/power/main.c) 等待其每一個孩子進入suspend狀態(tài)
                            dev->pm_domain->ops->suspend    或
                            dev->type->pm->suspend          或
                            dev->class->pm->suspend         或
                            dev->bus->pm->suspend           或
                            因此自己在寫驅(qū)動的時候可以在其pm_domain中或type->pm中或class->pm中或bus->pm中加入suspend函數(shù)
                suspend_enter //(kernel/power/suspend.c) 設(shè)備都進入suspend狀態(tài)了接下來就是CPU了
                    suspend_ops->prepare 單板的prepare函數(shù)若存在就調(diào)用
                    dpm_suspend_end //(drivers/base/power/main.c)
                        dpm_suspend_late //(drivers/base/power/main.c) 對全局靜態(tài)鏈表dpm_suspended_list中的每一個條目都調(diào)用device_suspend_late()
                            device_suspend_late() //(drivers/base/power/main.c) 調(diào)用此設(shè)備的
                                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 調(diào)用單板相關(guān)的函數(shù),可以做一些清理,若單板不需要也可以不實現(xiàn)它
                    disable_nonboot_cpus //(kernel/cpu.c) 多核Soc中非用于啟動內(nèi)核的CPU叫做nonboot_cpu,停止non-boot CPU
                    arch_suspend_disable_irqs  //(include/linux/suspend.h)//關(guān)閉中斷,extern的,Renesas上沒有實現(xiàn)
                    syscore_suspend    關(guān)閉核心模塊
                    suspend_ops->enter 調(diào)用單板相關(guān)的函數(shù),這里真正進入suspend狀態(tài)了,如三星的是s3c_pm_enter(),里面通過any_allowed()檢測有沒有設(shè)置喚醒源
                    若沒有設(shè)置是不允許睡眠的。這個函數(shù)下面單獨列出
===================================上面是休眠,下面就是喚醒操作了==================================
                    當(dāng)我們按下某個按鍵并且這個按鍵是喚醒源的話,就會喚醒CPU,從Uboot開始執(zhí)行
                    按鍵喚醒源 --> Uboot --> 讀寄存器GSTATUS3 --> 就會執(zhí)行s3c_cpu_resume()
                    syscore_resume //(drivers/base/syscore.c) 對全局鏈表syscore_ops_list中的每一個node都調(diào)用其resume()
                    arch_suspend_enable_irqs //(include/linux/suspend.h)
                    enable_nonboot_cpus //(kernel/cpu.c)
                    suspend_ops->wake 如果單板有對應(yīng)的wake()就調(diào)用
                    dpm_resume_start(PMSG_RESUME) //(drivers/base/power/main.c)
                        dpm_resume_noirq(state); //(drivers/base/power/main.c) 對全局鏈表dpm_noirq_list中的每一個設(shè)備都執(zhí)行device_resume_noirq
                            device_resume_noirq //(drivers/base/power/main.c) 對每一個設(shè)備都調(diào)用
                                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        或
                            執(zhí)行完resume_noirq的所有設(shè)備都會被放在全局鏈表dpm_late_early_list中
                            resume_device_irqs //(kernel/irq/pm.c)
                                resume_irqs //(kernel/irq/pm.c)
                                    __enable_irq //(kernel/irq/pm.c) 對全局數(shù)組irq_desc中的每一個irq都調(diào)用__enable_irq,但是Renesas的BSP沒有實現(xiàn),里面還有一個野指針

                        dpm_resume_early(state); //(drivers/base/power/main.c) 對全局鏈表dpm_late_early_list中的每一個元素都執(zhí)行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() 如果單板有對應(yīng)的finish()就調(diào)用,三星的對應(yīng)下面
                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都調(diào)用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        或                                    
                    然后將所有的設(shè)備移動到全局鏈表dpm_prepared_list中
                    dpm_complete //(drivers/base/power/main.c) 對全局鏈表dpm_prepared_list中的每一個設(shè)備都調(diào)用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) 關(guān)閉本地中斷獲取spin鎖后調(diào)用控制臺打印函數(shù)以poll方式打印內(nèi)核log
                suspend_ops->end() 如果單板有對應(yīng)的end()就調(diào)用
                suspend_ops->recover() 如果dpm_suspend_start失敗或者suspend_test失敗,單板有對應(yīng)的recover()就調(diào)用
            suspend_finish //(kernel/power/suspend.c)
                suspend_thaw_processes(); //(kernel/power/power.h)喚醒應(yīng)用程序
                pm_notifier_call_chain(PM_POST_SUSPEND); //(kernel/power/main.c) 通知關(guān)注這個事件的App程序,對全局pm_chain_head->head中的每一個都調(diào)用其notifier_call()
                pm_restore_console(); //(kernel/power/console.c)
返回用戶空間
s3c_pm_enter 展開:
    any_allowed 檢測有沒有設(shè)置喚醒源
    samsung_pm_save_gpios(); 保存一些狀態(tài),gpio uart狀態(tài)
    samsung_pm_saved_gpios();
    s3c_pm_save_uarts();
    s3c_pm_save_core();
    s3c_pm_configure_extint 配置一下喚醒源
    pm_cpu_prep s3c2410_pm_add()中對這個函數(shù)指針賦值
        s3c2410_pm_prepare 這個函數(shù)中將s3c_cpu_resume()這個函數(shù)的物理地址寫入了GSTATUS3寄存器中
            GSTATUS3 = s3c_cpu_resume() 當(dāng)系統(tǒng)被喚醒的時候會去執(zhí)行Uboot,Uboot中會去讀GSTATUS3得到一個函數(shù)的地址然后去執(zhí)行它
    s3c_pm_arch_stop_clocks 關(guān)閉時鐘
    cpu_suspend(0, pm_cpu_sleep) 這個是最重要的suspend函數(shù)了,參數(shù)2 pm_cpu_sleep 作為回調(diào)函數(shù)
        __cpu_suspend(arg, pm_cpu_sleep)  arch/arm/kernel/sleep.S 中是個匯編代碼,pm_cpu_sleep作為第二個參數(shù)傳入,就保存在r1里面
            stmfd    sp!, {r0, r1}        @ save suspend func arg and pointer
            ldmfd    sp!, {r0, pc}        @ call suspend fn  恢復(fù)的時候把r1里面的值恢復(fù)到pc指針中! 相當(dāng)于執(zhí)行了pm_cpu_sleep
        pm_cpu_sleep = ENTRY(s3c2410_cpu_suspend) /arch/arm/mach-s3c24xx/s3c2410.S
            先讀一遍這幾個寄存器,讀的原因:休眠的時候通過寫這個REFRESH寄存器把SDRAM給關(guān)掉,關(guān)閉掉后還要通過寫寄存器CLKCON把
            CPU給停掉,但是當(dāng)內(nèi)核去訪問這些寄存器的時候,內(nèi)核需要使用虛擬地址,就要使用到SDRAM上的頁表,但是現(xiàn)在已經(jīng)要先關(guān)閉SDRAM了,
            就不能再訪問SDRAM上的頁表了,因此需要先讀取一下那些寄存器,緩存這些寄存器的頁表到TLB里面,之后通過TLB得到翻譯后的這些寄存
            器的物理地址。
            ldr    r4, =S3C2410_REFRESH 
            ldr    r5, =S3C24XX_MISCCR
            ldr    r6, =S3C2410_CLKCON
            下面先去dummy執(zhí)行一遍,pc指針肯定也不等于0,目前先把這些指令緩存在I cache里面。
            teq    pc, #0            @ first as a trial-run to load cache
            bl    s3c2410_do_sleep
            這里就可以從Icache中獲取這些指令,執(zhí)行SDRAM的關(guān)閉和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();

流程分析結(jié)論:
1.PM Core會依次調(diào)用“prepare-->suspend-->suspend_late-->suspend_noirq-------wakeup--------->resume_noirq-->resume_early-->resume-->complete”
總體流程就是先讓你準備一下,然后再讓你休眠,休眠和喚醒是反著來的。在suspend之前可以做一些準備工作,suspend之后可以做一些清理函數(shù),若是認
2.為沒有什么好準備或清理的,設(shè)備驅(qū)動中可以不實現(xiàn)prepare函數(shù)和suspend_late函數(shù),只實現(xiàn)suspend函數(shù)。
3.可以參考函數(shù)dpm_show_time()里面的實現(xiàn)來在內(nèi)核中打印時間
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功能實現(xiàn)

/arch/arm/plat-samsung/pm.c 中的struct platform_suspend_ops s3c_pm_ops
s3c_pm_init
suspend_set_ops(&s3c_pm_ops);

設(shè)置喚醒源:配置GPIO引腳工作于中斷模式,設(shè)置它的觸發(fā)方式
s3c_pm_enter --> s3c_pm_configure_extint()

使用哪個交叉編譯工具鏈只需要在PATH中設(shè)置其路徑即可!有多個不同版本的話設(shè)置一個自己想要的版本的路徑到PATH中,就選用了這個交叉工具鏈。

是make uImage

修改按鍵驅(qū)動在request_irq()之后調(diào)用irq_set_irq_wake()來指定此中斷為喚醒源(沒有喚醒源是不允許進入睡眠模式的)

四、修改驅(qū)動支持電源管理
a.通知notifier
①由上流程分析可知在凍結(jié)App之前,使用pm_notifier_call_chain(PM_SUSPEND_PREPARE)來通知應(yīng)用程序
②由上流程分析可知在重啟App之后,使用pm_notifier_call_chain(PM_POST_SUSPEND);
如果驅(qū)動在凍結(jié)App之前之后有些事情要做,可以使用register_pm_notifier()注冊這兩個notifier
一般驅(qū)動進入休眠是在凍結(jié)進程之后進行休眠的,以免驅(qū)動休眠之后還有App訪問到它!

b.添加suspend,resume函數(shù),可以參考s3c2410fb.c
1.若在struct platform_driver里面加的話只有suspend和resume兩個選項,但是這里是內(nèi)核即將遺棄的,平臺(一般)設(shè)備沒有pm_domain、type、class,但是有總線bus

static struct platform_driver s3c2412fb_driver = {
    .probe        = s3c2412fb_probe,
    .remove        = s3c2410fb_remove,
    .suspend    = s3c2410fb_suspend,
    .resume        = s3c2410fb_resume,
    .driver        = { /*若此內(nèi)部有.pm域,優(yōu)先調(diào)用driver.pm,而不是platform_driver.suspend*/
        .name    = "s3c2412-lcd",
    },
};

平臺驅(qū)動struct platform_driver中注冊了suspend,resume函數(shù)何時被調(diào)用:
    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;[全局]
由上面的調(diào)用邏輯dev->bus->pm->suspend會被調(diào)用,而platform_bus_type;[全局]有.pm = &platform_dev_pm_ops,
platform_pm_suspend() --> platform_legacy_suspend() --> platform_driver.suspend.

2.添加suspend,resume函數(shù)
老方法:在platform_driver中實現(xiàn)suspend/resume方法
新方法:在platform_driver.driver.pm中實現(xiàn)suspend/resume方法,[可參考ac97c.c]

3.實驗現(xiàn)象
實現(xiàn)中休眠之前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時會傳入不同的參數(shù)調(diào)用這個函數(shù)。
    ......
    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實現(xiàn)

static int lcd_suspend(struct device *dev)
{
    /*1.保存LCD相關(guān)寄存器狀態(tài)*/

    /*2.關(guān)閉時鐘,斷電進入suspend狀態(tài)*/

    return 0;
}

static int lcd_resume(struct device *dev)
{
    /*1.還原LCD相關(guān)寄存器狀態(tài)*/

    /*2.使能時鐘,上電進入suspend狀態(tài)*/

    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");

總結(jié)

以上是生活随笔為你收集整理的Linux的系统suspend和resume的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。