一、设备IRQ的suspend和resume

主要解决这样一个问题:在系统休眠过程中,如何suspend设备中断(IRQ)?在从休眠中唤醒的过程中,如何resume设备IRQ?

一般而言,在系统suspend过程的后期,各个设备的IRQ (interrupt request line)会被disable掉。具体的时间点是在各个设备的late suspend阶段之后。代码如下(删除了部分无关代码):

  1. static int suspend_enter(suspend_state_t state, bool *wakeup)
  2. {……
  3. error = dpm_suspend_late(PMSG_SUSPEND);-----late suspend阶段
  4. error = platform_suspend_prepare_late(state);
  5. 下面的代码中会disable各个设备的irq
  6. error = dpm_suspend_noirq(PMSG_SUSPEND);----进入noirq的阶段
  7. error = platform_suspend_prepare_noirq(state);
  8. ……
  9. }

在dpm_suspend_noirq函数中,会针对系统中的每一个device,依次调用device_suspend_noirq来执行该设备noirq情况下的suspend callback函数,当然,在此之前会调用suspend_device_irqs函数来disable所有设备的irq。

之所以这么做,其思路是这样的:在各个设备驱动完成了late suspend之后,按理说这些已经被suspend的设备不应该再触发中断了。如果还有一些设备没有被正确的suspend,那么我们最好的策略是mask该设备的irq,从而阻止中断的递交。此外,在过去的代码中(指interrupt handler),我们对设备共享IRQ的情况处理的不是很好,存在这样的问题:在共享IRQ的设备们完成suspend之后,如果有中断触发,这时候设备驱动的interrupt handler并没有准备好。在有些场景下,interrupt handler会访问已经suspend设备的IO地址空间,从而导致不可预知的issue。这些issue很难debug,因此,我们引入了suspend_device_irqs()以及设备noirq阶段的callback函数。

系统resume过程中,在各个设备的early resume过程之前,各个设备的IRQ会被重新打开,具体代码如下(删除了部分无关代码):

  1. static int suspend_enter(suspend_state_t state, bool *wakeup)
  2. {……
  3. platform_resume_noirq(state);----首先执行noirq阶段的resume
  4. dpm_resume_noirq(PMSG_RESUME);------在这里会恢复irq,然后进入early resume阶段
  5. platform_resume_early(state);
  6. dpm_resume_early(PMSG_RESUME);
  7. ……}

在dpm_resume_noirq函数中,会调用各个设备驱动的noirq callback,在此之后,调用resume_device_irqs函数,完成各个设备irq的enable。

二、关于IRQF_NO_SUSPEND Flag

当然,有些中断需要在整个系统的suspend-resume过程中(包括在noirq阶段,包括将nonboot CPU推送到offline状态以及系统resume后,将其重新设置为online的阶段)保持能够触发的状态。一个简单的例子就是timer中断,此外IPI以及一些特殊目的设备中断也需要如此。

在中断申请的时候,IRQF_NO_SUSPEND flag可以用来告知IRQ subsystem,这个中断就是上一段文字中描述的那种中断:需要在系统的suspend-resume过程中保持enable状态。有了这个flag,suspend_device_irqs并不会disable该IRQ,从而让该中断在随后的suspend和resume过程中,保持中断开启。当然,这并不能保证该中断可以将系统唤醒。如果想要达到唤醒的目的,请调用enable_irq_wake。

需要注意的是:IRQF_NO_SUSPEND flag影响使用该IRQ的所有外设(一个IRQ可以被多个外设共享,不过ARM中不会这么用)。如果一个IRQ被多个外设共享,并且各个外设都注册了对应的interrupt handler,如果其一在申请中断的时候使用了IRQF_NO_SUSPEND flag,那么在系统suspend的时候(指suspend_device_irqs之后,按理说各个IRQ已经被disable了),所有该IRQ上的各个设备的interrupt handler都可以被正常的被触发执行,即便是有些设备在调用request_irq(或者其他中断注册函数)的时候没有设定IRQF_NO_SUSPEND flag。正因为如此,我们应该尽可能的避免同时使用IRQF_NO_SUSPEND 和IRQF_SHARED这两个flag。

三、系统中断唤醒接口:enable_irq_wake() 和 disable_irq_wake()

有些中断可以将系统从睡眠状态中唤醒,我们称之“可以唤醒系统的中断”,当然,“可以唤醒系统的中断”需要配置才能启动唤醒系统这样的功能。这样的中断一般在工作状态的时候就是作为普通I/O interrupt出现,只要在准备使能唤醒系统功能的时候,才会发起一些特别的配置和设定。

这样的配置和设定有可能是和硬件系统(例如SOC)上的信号处理逻辑相关的,我们可以考虑下面的HW block图:

外设的中断信号被送到“通用的中断信号处理模块”和“特定中断信号接收模块”。正常工作的时候,我们会turn on“通用的中断信号处理模块”的处理逻辑,而turn off“特定中断信号接收模块” 的处理逻辑。但是,在系统进入睡眠状态的时候,有可能“通用的中断信号处理模块”已经off了,这时候,我们需要启动“特定中断信号接收模块”来接收中断信号,从而让系统suspend-resume模块(它往往是suspend状态时候唯一能够工作的HW block了)可以正常的被该中断信号唤醒。一旦唤醒,我们最好是turn off“特定中断信号接收模块”,让外设的中断处理回到正常的工作模式,同时,也避免了系统suspend-resume模块收到不必要的干扰。

IRQ子系统提供了两个接口函数来完成这个功能:enable_irq_wake()函数用来打开该外设中断线通往系统电源管理模块(也就是上面的suspend-resume模块)之路,另外一个接口是disable_irq_wake(),用来关闭该外设中断线通往系统电源管理模块路径上的各种HW block。

调用了enable_irq_wake会影响系统suspend过程中的suspend_device_irqs处理,代码如下:

  1. static bool suspend_device_irq(struct irq_desc *desc)
  2. {
  3. ……
  4. if (irqd_is_wakeup_set(&desc->irq_data)) {
  5. irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
  6. return true;
  7. }
  8. 省略Disable 中断的代码
  9. }

也就是说,一旦调用enable_irq_wake设定了该设备的中断作为系统suspend的唤醒源,那么在该外设的中断不会被disable,只是被标记一个IRQD_WAKEUP_ARMED的标记。对于那些不是wakeup source的中断,在suspend_device_irq 函数中会标记IRQS_SUSPENDED并disable该设备的irq。在系统唤醒过程中(resume_device_irqs),被diable的中断会重新enable。

当然,如果在suspend的过程中发生了某些事件(例如wakeup source产生了有效信号),从而导致本次suspend abort,那么这个abort事件也会通知到PM core模块。事件并不需要被立刻通知到PM core模块,一般而言,suspend thread会在某些点上去检查pending的wakeup event。

在系统suspend的过程中,每一个来自wakeup source的中断都会终止suspend过程或者将系统唤醒(如果系统已经进入suspend状态)。但是,在执行了suspend_device_irqs之后,普通的中断被屏蔽了,这时候,即便HW触发了中断信号也无法执行其interrupt handler。作为wakeup source的IRQ会怎样呢?虽然它的中断没有被mask掉,但是其interrupt handler也不会执行(这时候的HW Signal只是用来唤醒系统)。唯一有机会执行的interrupt handler是那些标记IRQF_NO_SUSPEND flag的IRQ,因为它们的中断始终是enable的。当然,这些中断不应该调用enable_irq_wake进行唤醒源的设定。

四、Interrupts and Suspend-to-Idle

Suspend-to-idle (也被称为"freeze" 状态)是一个相对比较新的系统电源管理状态,相关代码如下:

  1. static int suspend_enter(suspend_state_t state, bool *wakeup)
  2. {
  3. ……
  4. 各个设备的late suspend阶段
  5. 各个设备的noirq suspend阶段
  6. if (state == PM_SUSPEND_FREEZE) {
  7. freeze_enter();
  8. goto Platform_wake;
  9. }
  10. ……
  11. }

Freeze和suspend的前面的操作基本是一样的:首先冻结系统中的进程,然后是suspend系统中的形形色色的device,不一样的地方在noirq suspend完成之后,freeze不会disable那些non-BSP的处理器和syscore suspend阶段,而是调用freeze_enter函数,把所有的处理器推送到idle状态。这时候,任何的enable的中断都可以将系统唤醒。而这也就意味着那些标记IRQF_NO_SUSPEND(其IRQ没有在suspend_device_irqs过程中被mask掉)是有能力将处理器从idle状态中唤醒(不过,需要注意的是:这种信号并不会触发一个系统唤醒信号),而普通中断由于其IRQ被disable了,因此无法唤醒idle状态中的处理器。

那些能够唤醒系统的wakeup interrupt呢?由于其中断没有被mask掉,因此也可以将系统从suspend-to-idle状态中唤醒。整个过程和将系统从suspend状态中唤醒一样,唯一不同的是:将系统从freeze状态唤醒走的中断处理路径,而将系统从suspend状态唤醒走的唤醒处理路径,需要电源管理HW BLOCK中特别的中断处理逻辑的参与。

五、IRQF_NO_SUSPEND 标志和enable_irq_wake函数不能同时使用

针对一个设备,在申请中断的时候使用IRQF_NO_SUSPEND flag,又同时调用enable_irq_wake设定唤醒源是不合理的,主要原因如下:

1、如果IRQ没有共享,使用IRQF_NO_SUSPEND flag说明你想要在整个系统的suspend-resume过程中(包括suspend_device_irqs之后的阶段)保持中断打开以便正常的调用其interrupt handler。而调用enable_irq_wake函数则说明你想要将该设备的irq信号设定为中断源,因此并不期望调用其interrupt handler。而这两个需求明显是互斥的。

2、IRQF_NO_SUSPEND 标志和enable_irq_wake函数都不是针对一个interrupt handler的,而是针对该IRQ上的所有注册的handler的。在一个IRQ上共享唤醒源以及no suspend中断源是比较荒谬的。

不过,在非常特殊的场合下,一个IRQ可以被设定为wakeup source,同时也设定IRQF_NO_SUSPEND 标志。为了代码逻辑正确,该设备的驱动代码需要满足一些特别的需求。

本文作者:佚名
来源:51CTO

Linux系统休眠(System Suspend)和设备中断处理相关推荐

  1. 启明云端分享|Linux系统下如何调试I2C设备

    提示:Linux系统中,一些传感器通常挂载在I2C总线上,比如RTC和CSI等.我们在调试这些设备的时候,就需要了解Linux系统中操作I2C总线的方法.本文将详细介绍在Linux系统中如何调试I2C ...

  2. Linux系统中判断多个设备使用了同一个IP地址,判断IP地址冲突的问题

    在Linux系统中,如果多个设备使用了同一个IP地址,就会发生IP地址冲突的问题.IP地址冲突会导致网络连接不稳定,甚至会导致网络中断.因此,在使用Linux系统时,需要及时发现和解决IP地址冲突问题 ...

  3. 【linux系统编程】进程间通信:信号中断处理

    什么是信号? 信号是 Linux 进程间通信的最古老的方式.信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 .信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断, ...

  4. linux禁止系统休眠,让linux系统休眠

    "iPad占有68%的市场份额,但是却占有平板市场91%的Web流量."我不知道其他平板都在做什么!"" – Tim Cook "在休眠(画外音)&q ...

  5. 【Linux系统编程应用】 Linux系统中找不到设备/dev/fb0

    [重新编译内核的方法] 如果/dev/没有fb0设备的话,关键在于内核里面是否加载了此设备,如果加载此设备,则根据对应的主设备号使用mknod来创建设备,否则如果内核没有加载此设备即使创建100个设备 ...

  6. linux ssd硬盘做缓存,linux系统中ssd当块设备缓存

    原理 写操作先缓存到ssd硬盘上,然后通过一定策略写到普通硬盘上: 读操作热点数据可以缓存到ssd硬盘上,提高读取数据的速度. 软件 ssd 缓存开源软件主要有bcache ,flashcache b ...

  7. linux 系统迁移到固态硬盘,在Linux系统中将SSD当块设备缓存的方法

    原理 写操作先缓存到ssd硬盘上,然后通过一定策略写到普通硬盘上: 读操作热点数据可以缓存到ssd硬盘上,提高读取数据的速度. 软件 ssd 缓存开源软件主要有bcache ,flashcache b ...

  8. Linux系统休眠、待机自动化测试脚本

    待机(S3)自动化测试脚本如下: #!/bin/bashread -p "请输入待机次数:" number echo "3秒后开始待机" sleep 3 num ...

  9. linux系统用什么手柄,如何直接在Linux系统中处理来自角色设备/游戏手柄的输入?...

    我正在开发一个C程序,它使用 USB SNES controller输入基于RPM的分发.有没有人知道的库可以让它更容易与之交互,或者某些工具(joydev?)允许从设备中正确读取输入?我不需要整个游 ...

最新文章

  1. 万字长文带你看尽深度学习中的各种卷积网络
  2. OpenGL_Qt学习笔记之_05(纹理映射)(转)
  3. java数据访问策略_java – 支持基于最近访问过的项目的高效启动策略的数据结构...
  4. Python的捕虫笔记
  5. js笔记(10)之无缝滚动
  6. 看看自己08年的基金是否能赚钱
  7. 为SharePoint Services (SPS)设置文件图标
  8. JavaScript--eval
  9. oracle查询多个加锁,解析oracle对select加锁的方法以及锁的查询 转
  10. 优化器,sgd,adam等
  11. 实验三.局域网的组建
  12. 使用IPV6技术访问网站
  13. Android跳转应用市场更新自己《完美适配大部分手机应用商店》
  14. 技巧:XP桌面图标阴影的去除
  15. java中类成员,java中类成员的限定词
  16. Android EditText优先弹出数字输入法
  17. 【转】太用力的人跑不远
  18. 【Java】浅谈Swing概况及简单窗口的构建
  19. 苹果A15仿生芯片集成150亿个晶体管,较A14增加近30%
  20. COJ 1163: 寒衣调

热门文章

  1. Expressions are not allowed at the top level
  2. 众多Android 开源项目推荐,给力工作给力学习
  3. ListBox,CheckBoxList,DropDownList,RadioButtonList的常见用法
  4. 计算机成绩表用函数怎么做,题用Excel函数以计算机成绩为依据计算出等次,怎么用函数IF 设定三个分类,如》90 为优秀 79~89为良好其余合格...
  5. python必学的模块_Python常用的模块
  6. poj2112 二分最大流+Floyd
  7. 【五线谱】拍号与音符时值 ( 五线谱拍号 | 全音符休止符 | 二分音符休止符 | 四分音符休止符 | 八分音符休止符 | 十六分音符休止符 | 三十二分音符休止符 )
  8. 【Groovy】Groovy 脚本调用 ( Linux 中调用 Groovy 脚本 | Windows 中调用 Groovy 脚本 )
  9. 【C 语言】文件操作 ( 按照单个字符的方式读写文件 | fgetc 函数 | fputc 函数 )
  10. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )