• 3、系统睡眠模型


    http://www.wowotech.net/linux_kenrel/suspend_and_resume.html这篇文章很经典,可以先参考

    我们的驱动如果需要支持休眠和唤醒,需要添加suspend函数和resume函数

    1. suspend流程
    启动 suspend to ram:(睡眠)//suspend to disk(休眠)
    echo mem > /sys/power/state //启动休眠的方法就是往/sys/power/state 写一个任意的字符串,会调用state_store函数(还有个state_show是读的时候被调用 )

    这些对文件操作和函数是怎么关联起来的,通过宏power_attr(state),展开后就相当于定义了一个结构体类型

    static struct kobj_attribute state_attr = {
      .attr = {
      .name = __stringify("state"),
    .  mode = 0644,
      },
      .show = state_show,
      .store =state_store,
    }

    power_attr(state)在一个struct attribution *g[]数组中被使用,g数组又被struct attribution_group attr_group结构体中使用,attr_group在pm_init这个入口函数中通过sysfs_create_group在power下创建了一个名为state函数,对其读写会调用专门的函数

    ------------------------------
    state_store (kernel/power/main.c)
      pm_suspend (kernel/power/suspend.c)
        enter_state (kernel/power/suspend.c)//进入某种状态(on、standby、suspend)
          suspend_prepare (kernel/power/suspend.c)//做一些这边工作
            pm_prepare_console (kernel/power/console.c)//
            pm_notifier_call_chain(PM_SUSPEND_PREPARE); (kernel/power/main.c) // 通知所有关心"休眠消息"的驱动程序
            suspend_freeze_processes (kernel/power/power.h) // 冻结APP和内核线程
          suspend_devices_and_enter (kernel/power/suspend.c) // 让设备进入休眠状态
            suspend_ops->begin // 如果平台相关的代码有begin函数就去调用它 ,suspend_ops在suspend.c中的suspend_set_ops中被设置,其被archarmplat-samsungpm.c中的s3c_pm_init调用
            suspend_console (kernel/power/suspend.c)//打印“printk串口打印将不可用,会关闭串口”
            dpm_suspend_start(PMSG_SUSPEND); (drivers/base/power/main.c)
              dpm_prepare(state); (drivers/base/power/main.c)

                用platform_device_register->platform_device_add->device_add->device_pm_add->list_add_tail(&dev->power.entry, &dpm_list);来把dev挂到dpm_list链表上
                对于dmp_list链表中的每一个设备,都调用device_prepare(dev, state);//dmp_list链表上的设备处理完后会把设备放入dpm_prepared_list链表
                  对于该设备,调用它的dev->pm_domain->ops->prepare 或
                           dev->type->pm->prepare 或
                           dev->class->pm->prepare 或
                           dev->bus->pm->prepare 或
                           dev->driver->pm->prepare
              dpm_suspend(state); (drivers/base/power/main.c) // 让各类设备休眠
                对于dpm_prepared_list链表中的每一个设备,都调用device_suspend(dev);//dpm_prepared_list上的链表被处理后会放入dpm_suspended_list链表
                  __device_suspend(dev, pm_transition, false);

                    dpm_wait_for_children//等它的孩子先休眠
                    对于该设备,调用它的dev->pm_domain->ops->suspend 或
                           dev->type->pm->suspend 或
                           dev->class->pm->suspend 或
                           dev->bus->pm->suspend 或
                           dev->driver->pm->suspend

            suspend_enter(state, &wakeup) (kernel/power/suspend.c)//处理CPU休眠
               suspend_ops->prepare // 即s3c_pm_prepare
               dpm_suspend_end(PMSG_SUSPEND); (drivers/base/power/main.c)
                  dpm_suspend_late(state); (drivers/base/power/main.c)
                    对于dpm_suspended_list链表中的每一个设备,都调用device_suspend_late(dev, state);//dpm_suspended_list链表上的dev被处理完后放入dpm_late_early_list链表
                      对于该设备,调用它的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
                               dpm_suspend_noirq
                    对于dpm_late_early_list链表中的每一个设备,都调用device_suspend_noirq(dev, state);//dpm_late_early_list链表上的dev被处理完后放入dpm_noirq_list链表
                      对于该设备,调用它的dev->pm_domain->ops->suspend_noirq 或
                               dev->type->pm->suspend_noirq 或
                               dev->class->pm->suspend_noirq 或
                               dev->bus->pm->suspend_noirq 或
                               dev->driver->pm->suspend_noirq
                suspend_ops->prepare_late() //
                disable_nonboot_cpus(); //关掉多个nonboot_cpu,对应多核cpu,除了唤醒用的cpu,其他cpu被称为nonboot
                arch_suspend_disable_irqs();//关闭中断
                syscore_suspend//关闭核心模块
                suspend_ops->enter(state); // s3c_pm_enter (archarmplat-samsungpm.c) //真正的进入休眠状态,里面的步骤和上节在uboot中实现的休眠动作一样 
                  ......//一大堆准备工作
                  pm_cpu_prep // 在c文件中把其复制为s3c2410_pm_prepare (archarmmach-s3c24xxpm-s3c2410.c)
                    在s3c2410_pm_prepare 函数中赋值GSTATUS3 = s3c_cpu_resume//设置唤醒后运行的函数地址

                  ......
                  cpu_suspend(0, pm_cpu_sleep); // archarmkernelsleep.S,最重要的休眠函数,是通过汇编实现调用的时候第一个参数保存在r0里面,第二个参数保存在r1里面(ldmfd sp!,{r0,pc}   从栈中把r1复制给pc去执行该函数)
                    pm_cpu_sleep (archarmmach-s3c24xxpm-s3c2410.c) //分析汇编cpu_suspend就是调用pm_cpu_sleep ,其被赋值s3c2410_cpu_suspend
                      s3c2410_cpu_suspend (archarmmach-s3c24xxsleep-s3c2410.S),这个函数就是休眠过程的8,9,10,11,12
                  以上是休眠过程
                  ===================================
                  下面开始唤醒过程(唤醒过程执行的就是休眠过程的相反步骤)
                  按键唤醒, 导致u-boot运行, 读取GSTATUS3, 执行s3c_cpu_resume,接着会从上面的cpu_suspend之后运行
                  .....
                  s3c_pm_restore_core
                syscore_resume//启动核心模块
                arch_suspend_enable_irqs//打开中断
                enable_nonboot_cpus//启动nonboot_cpu
                suspend_ops->wake//调用平台唤醒函数
                dpm_resume_start(PMSG_RESUME);
                  dpm_resume_noirq(state);
                    对于dpm_noirq_list链表中的每一个设备,调用device_resume_noirq(dev, state);//dpm_noirq_list链表上的dev被处理完后放入dpm_late_early_list链表
                      对于该设备,调用它的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
                  dpm_resume_early(state);
                    对于dpm_late_early_list链表中的每一个设备,调用device_resume_early(dev, state);//dpm_late_early_list链表上的dev被处理完后放入dpm_noirq_list链表
                      对于该设备,调用它的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函数
                    s3c_pm_finish

            dpm_resume_end(PMSG_RESUME);//完成设备唤醒工作
            resume_console();//唤醒串口
          suspend_finish();//平台唤醒完成,比如samung
            suspend_thaw_processes();//唤醒应用程序
            pm_notifier_call_chain(PM_POST_SUSPEND);//通知各类驱动程序
            pm_restore_console();
          //返回用户空间


    驱动程序里相关的电源管理函数的调用过程:(一般驱动休眠和唤醒只需要支持suspend和resume就可以了)
    休眠: prepare—>suspend—>suspend_late—>suspend_noirq
    唤醒: resume_noirq—>resume_early—>resume-->complete


    参考文章:
    实现流程: Linux电源管理(6)_Generic PM之Suspend功能
    http://www.wowotech.net/linux_kenrel/suspend_and_resume.html

    驱动所涉及的接口: Linux电源管理(4)_Power Management Interface
    http://www.wowotech.net/linux_kenrel/pm_interface.html


    archarmplat-samsungpm.c
    s3c_pm_init
      suspend_set_ops(&s3c_pm_ops);
        suspend_ops = ops


    2. 修改内核或驱动以使用suspend功能
    设置唤醒源: 配置GPIO引脚工作于中断功能, 设置它的触发方式

    怎样修改内核:
    a. 通过调用s3c_irq_wake来修改s3c_irqwake_intmask、s3c_irqwake_eintmask两个变量用来表示唤醒源是哪个

    如果不设置s3c_irqwake_intmask、s3c_irqwake_eintmask,在s3c_pm_enter函数中就会返回错误,因为没有设置唤醒源

    b. 需要自己设置GPIO用于中断功能,并设置它的触发方式

    int0,1,2,3这四个中断在s3c_irqwake_intmask中被设置
    eint4,5,...15等外部中断在s3c_irqwake_eintmask中被设置

    在我们的按键驱动中: request_irq之后调用s3c_irq_wake设置s3c_irqwake_intmask或s3c_irqext_wake来设置s3c_irqwake_eintmask

    2.1 直接修改内核: s3c_irqwake_intmask、s3c_irqwake_eintmask, 并且配置GPIO

    2.2 修改按键驱动,在request_irq之后调用irq_set_irq_wake

    2.1
    解压新工具链并设置PATH
    sudo tar xjf arm-linux-gcc-4.3.2.tar.bz2 -C /
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/usr/local/arm/4.3.2/bin

    编译内核:(打补丁,使用alsa声卡那节最后的内核补丁)
    tar xjf linux-3.4.2.tar.bz2
    cd linux-3.4.2
    patch -p1 < ../linux-3.4.2_alsa_wm8976_uda1341_jz2440_mini2440_tq2440.patch
    cp config_wm8976_jz2440 .config
    make menuconfig(配置支持电源管理Power management options->Suspend to RAM and standby)

    修改archarmplat-samsungpm.c

    unsigned long s3c_irqwake_intmask = 0xfffffffa;(设置EINT0和EINT2)
    unsigned long s3c_irqwake_eintmask = 0xfffff7ff;(设置EINT11)

    修改archarmplat-s3c24xxpm.c /* 实验发现不修改这个文件也成功,原因在于UBOOT已经配置了GPIO,设置了中断触发类型 */
    if (!irqstate) {
      if (pinstate == S3C2410_GPIO_IRQ)
        S3C_PMDBG("Leaving IRQ %d (pin %d) as is ", irq, pin);
    +   else{
    +      s3c_gpio_cfgpin(pin, S3C2410_GPIO_IRQ);//如果GPIO不是中 断模式需要设置它处于中断模式
    +  }
    /* 配置触发方式 */(芯片上电的时候已经有默认的触发方式了(低电平触发),所有这里可以不用设置了)
    } else {

    make uImage && cp arch/arm/boot/uImage /work/nfs_root

    sudo tar xjf fs_mini_mdev_new.tar.bz2 -C /work/nfs_root/

    使用新内核启动
    set bootcmd 'nfs 30000000 192.168.1.124:/work/nfs_root/uImage; bootm 30000000'
    set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.124:/work/nfs_root/fs_mini_mdev_new ip=192.168.1.17

    测试:
    cat /sys/power/state
    echo mem > /sys/power/state
    按键换醒

    (唤醒的时候发现系统崩溃,发现是在调用snd_soc_wm8976_resume函数是调用codec->write出错,是因为write函数的第一个参数之前是codec->control_data有问题,改为codec就可以了)

    (幸运的是这里有打印出错信息,我们查看arch/arm/plat-samsung/pm.c中打印都是通过S3C_PMDBG()来打印,查看打印是在在哪定义,其依赖那个配置项,发现是在pm.h中的#ifdef CONFIG_SAMSUNG_PM_DEBUG,在去内核中配置)


    2.2 在驱动程序中,注册 request_irq之后调用irq_set_irq_wake来指定唤醒源

    看课程测试驱动中代码是在buttons_init()中调用如下代码:(驱动退出的时候需要恢复)

    irq_set_irq_wake(IRQ_EINT0, 1);
    irq_set_irq_wake(IRQ_EINT2, 1);
    irq_set_irq_wake(IRQ_EINT11, 1);

    (修改完后执行驱动或者加入内核启动运行,在执行休眠唤醒动作,能休眠和唤醒)

    3. 修改驱动程序支持电源管理
    a. 通知notifier:
    在冻结APP之前,使用pm_notifier_call_chain(PM_SUSPEND_PREPARE)来通知驱动程序
    在重启APP之后,使用pm_notifier_call_chain(PM_POST_SUSPEND)来通知驱动程序

    如果驱动程序有事情在上述时机要处理,可以使用register_pm_notifier注册一个notifier

    b. 添加suspend, resume函数(驱动休眠必须发生在APP冻结之后,否则如果驱动休眠了,APP使用驱动就会出错,所有驱动休眠放在suspend中)

    (在内核只带的s3c2410fb.c驱动中,一般在register_device的时候在其platform_device_add中指定了dev->bus = platrom_bus_type,在platrom_bus_type中有个pm结构体,休眠的时候会调用里面的suspend,这个suspend会先判断platform_driver下的driver中是否有pm->suspend,否则调用老的处理函数platform_legacy_suspend,这个函数会调用platform_driver下的suspend,这就和上面分析的代码对应起来了)(新的内核推荐把suspend和resume放到platform_driver下的driver中)

    b.1 添加一个同名platform_device, platform_driver
    b.2 老方法:在platform_driver里实现suspend,resume成员
    新方法:在platform_driver里的driver里的pm结构体, 实现suspend,resume成员

    对于LCD, 配置内核去掉 CONFIG_FRAMEBUFFER_CONSOLE, 可以在休眠-唤醒后保留LCD上的图像
    应该也可以通过APP禁止Framebuffer用作console(否则唤醒后lcd上数据都丢失了,原因是lcd作为控制台的原因,在休眠之后唤醒会把console重新初始化)

    Device Drivers

      Graphics support

        Console display driver support

          <>Framebuffer Console support 

    系统处于运行状态并且LCD打开时, 耗电240mA
    休眠时, 耗电50mA

    insmod buttons.ko

    echo mem  >/sys/power/state   休眠

    按下按键后唤醒

    两篇文章:
    http://blog.csdn.net/bingqingsuimeng/article/details/7935414
    http://www.wowotech.net/linux_kenrel/suspend_and_resume.html

     

  • 相关阅读:
    几个不同的关键XPath概念
    go get 下载的包放在哪里呢?
    之前写的关于chromedp的文章被别人转到CSDN,很受鼓励,再来一篇golang爬虫实例
    微信小程序填坑之旅(2)-wx.showLoading的时候,仍能点击穿透,造成重复点击button的问题
    微信小程序填坑之旅(1)-app.js中用云开发获取openid,在其他页上用app.globaldata.openid获取为空
    JS 定时器-setInterval、clearInterval、setTimeout
    微信小程序开发入门教程(四)---自己动手做个小程序
    MT【247】恒成立画图像
    MT【246】方程根$acksim$图像交点
    MT【245】小概率事件
  • 原文地址:https://www.cnblogs.com/liusiluandzhangkun/p/8975097.html
Copyright © 2020-2023  润新知