参考:

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 = ,
},
.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流程分析

//include\linux\suspend.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 = ,
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];//
suspend_state_t mem_sleep_current = PM_SUSPEND_TO_IDLE; //
suspend_state_t mem_sleep_default = PM_SUSPEND_MAX;//
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(, 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, # @ 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\n");
return NOTIFY_OK;
case PM_POST_SUSPEND:
printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
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 ;
} 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 ;
} static int lcd_resume(struct device *dev)
{
/*1.还原LCD相关寄存器状态*/ /*2.使能时钟,上电进入suspend状态*/ return ;
} 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 ;
} 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的更多相关文章

  1. Linux下USB suspend/resume源码分析【转】

    转自:http://blog.csdn.net/aaronychen/article/details/3928479 Linux下USB suspend/resume源码分析 Author:aaron ...

  2. Linux内核系统体系概述

    Linux 内核主要由 5 个模块构成,它们分别是: 进程调度模块 用来负责控制进程对 CPU 资源的使用.所采取的调度策略是各进程能够公平合理地访问 CPU,同时保证内核能及时地执行硬件操作. 内存 ...

  3. [转帖]Linux内核系统体系概述

    Linux内核系统体系概述 https://www.cnblogs.com/alantu2018/p/8447369.html Linux 内核主要由 5 个模块构成,它们分别是: 进程调度模块 用来 ...

  4. Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated ?

    Thread.stop, Thread.suspend, Thread.resume被标记为废弃的方法.在查看JDK的文档时,提到了下面的参考文章,先是英文版,接着是中文翻译. Why is Thre ...

  5. 《Linux/Unix系统编程手册》 时间子系统

    Linux下操作系统编程有两本经典APUE即<Advanced Programming in the UNIX Environment>和TLPI<The Linux Program ...

  6. 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll

    关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...

  7. Linux regulator系统

    1. 概念:Regulator : 电源芯片, 比如电压转换芯片Consumer : 消费者,使用电源的部件, Regulator是给Consumer供电的machine : 单板,上面焊接有Regu ...

  8. Linux查看系统状态命令

    Linux查看系统状态命令       iostat iostat 命令详细地显示了存储子系统方面的情况.你通常用iostat来监控存储子系统总体上运行状况如何,并且在用户注意到服务器运行缓慢之前提早 ...

  9. 82 fsck-检查与修复 Linux 档案系统

    Linux fsck命令用于 检查与修复 Linux 档案系统,可以同时检查一个或多个 Linux 档案系统. 语法 fsck [-sACVRP] [-t fstype] [--] [fsck-opt ...

随机推荐

  1. Learning Query and Document Similarities from Click-through Bipartite Graph with Metadata

    读了一篇paper,MSRA的Wei Wu的一篇<Learning Query and Document Similarities from Click-through Bipartite Gr ...

  2. hdu5141 线段树

    这题说的是给了一串然后计算出这个串的最长递增子序列的长度,然后计算出有过少个子串[i,j] 他们的最长递增子序列和这整个子串的最长递增子序列相同,我们对于每个j最长递增子序列找出他在序列中的使成为最长 ...

  3. uva672

      Gangsters  N gangsters are going to a restaurant. The i-th gangster comes at the time Ti and has t ...

  4. ubuntu下 adb devices找不到devices

    不同手机用数据线与ubuntu连接后,执行adb devices,好多出现找不到devices的情况. 这里解决措施: 1. 执行lsusb(连接手机前与后) 找到显示内容的差异项: root@loc ...

  5. 【postman】谷歌postman插件的基本选项含义

    1.form-data:  就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件 ...

  6. python 简单的猜数字游戏

    !/usr/bin/env python --encoding:utf-8-- import random think=random.randint(1,10) print ("...... ...

  7. python 获取昨天今天明天日期

    import datetime today = datetime.date.today() yesterday = today - datetime.timedelta(days = ) tomorr ...

  8. Observer(观察者)

    意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新. 适用性: 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面.将这二者封装在独立 ...

  9. HTML之页面镶嵌体验

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. Java Spring-传统AOP开发

    2017-11-10 17:25:48 Spring中通知Advice类型(增强代码): 前置通知,org.springframework.aop.MethodBeforeAdvice:方法前 后置通 ...