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中被设置,其被arch\arm\plat-samsung\pm.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 (arch\arm\plat-samsung\pm.c) //真正的进入休眠状态,里面的步骤和上节在uboot中实现的休眠动作一样 
              ......//一大堆准备工作
              pm_cpu_prep // 在c文件中把其复制为s3c2410_pm_prepare (arch\arm\mach-s3c24xx\pm-s3c2410.c)
                在s3c2410_pm_prepare 函数中赋值GSTATUS3 = s3c_cpu_resume//设置唤醒后运行的函数地址

              ......
              cpu_suspend(0, pm_cpu_sleep); // arch\arm\kernel\sleep.S,最重要的休眠函数,是通过汇编实现调用的时候第一个参数保存在r0里面,第二个参数保存在r1里面(ldmfd sp!,{r0,pc}   从栈中把r1复制给pc去执行该函数)
                pm_cpu_sleep (arch\arm\mach-s3c24xx\pm-s3c2410.c) //分析汇编cpu_suspend就是调用pm_cpu_sleep ,其被赋值s3c2410_cpu_suspend
                  s3c2410_cpu_suspend (arch\arm\mach-s3c24xx\sleep-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


 

 

arch\arm\plat-samsung\pm.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)

修改arch\arm\plat-samsung\pm.c

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

修改arch\arm\plat-s3c24xx\pm.c /* 实验发现不修改这个文件也成功,原因在于UBOOT已经配置了GPIO,设置了中断触发类型 */
if (!irqstate) {
  if (pinstate == S3C2410_GPIO_IRQ)
    S3C_PMDBG("Leaving IRQ %d (pin %d) as is\n", 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

 

 

posted on 2018-04-30 22:57  拉风摊主  阅读(798)  评论(0编辑  收藏  举报

导航