当前位置: 首页 > 科技观察

Linux系统休眠(SystemSuspend)与设备中断处理

时间:2023-03-11 23:01:11 科技观察

1、设备IRQ的挂起和恢复主要解决这样一个问题:系统休眠时如何挂起设备中断(IRQ)?在休眠唤醒过程中,如何恢复设备IRQ?一般来说,在系统挂起过程的后期,各个设备的IRQ(中断请求线)都会被禁用。具体时间点在各个设备的后期挂起阶段之后。代码如下(删除了一些不相关的代码):staticintsuspend_enter(suspend_state_tstate,bool*wakeup){……error=dpm_suspend_late(PMSG_SUSPEND);-----latesuspendphaseerror=platform_suspend_prepare_late(state);下面的代码会禁用各个设备irqerror=dpm_suspend_noirq(PMSG_SUSPEND);————进入noirq阶段要执行设备noirq的suspend回调函数,当然要在调用suspend_device_irqs函数之前禁用所有设备的irq。这样做的原因是这样的:在每个设备驱动程序完成latesuspend之后,按理说这些挂起的设备应该不会再触发中断了。如果还有一些设备没有被正确挂起,那么我们最好的策略就是屏蔽设备的irq,从而阻止中断的传递。另外,在过去的代码中(指中断处理程序),我们并没有很好地处理设备共享IRQ的情况。有这样一个问题:共享IRQ的设备完成挂起后,如果有中断触发,那么设备驱动程序的中断处理程序还没有准备好。在某些场景下,中断处理程序会访问挂起设备的IO地址空间,从而导致不可预知的问题。这些问题很难调试,所以我们引入了suspend_device_irqs()和devicenoirq阶段的回调函数。在系统resume过程中,在每个设备的earlyresume过程之前,每个设备的IRQ都会被重新打开。具体代码如下(删除一些不相关的代码):staticintsuspend_enter(suspend_state_tstate,bool*wakeup){……platform_resume_noirq(state);----先在noirq阶段执行resumedpm_resume_noirq(PMSG_RESUME);------这里会恢复irq,然后进入earlyresume阶段platform_resume_early(state);dpm_resume_early(PMSG_RESUME);...}中dpm_resume_noirq函数,它会调用各个设备驱动的noirq回调,之后调用resume_device_irqs函数完成各个设备irq的使能。2.关于IRQF_NO_SUSPENDFlag当然,在整个系统的suspend-resume过程中(包括noirq阶段,包括将nonbootCPU推到offline状态,系统重启后复位到online阶段,需要触发一些中断恢复)状态。一个简单的例子就是定时器中断,除了IPI,还需要一些特殊用途的设备中断。在中断应用程序时,可以通过IRQF_NO_SUSPEND标志通知IRQ子系统,这个中断就是上一段描述的那种中断:需要在系统的suspend-resume过程中保持enable状态。有了这个标志,suspend_device_irqs就不会禁用IRQ,这样在后续的suspend和resume过程中,中断会一直保持中断使能状态。当然,这并不能保证中断一定能唤醒系统。如果要唤醒,请调用enable_irq_wake。需要注意的是IRQF_NO_SUSPEND标志影响所有使用IRQ的外设(一个IRQ可以被多个外设共享,但在ARM中没有使用)。如果一个IRQ被多个外设共享,并且每个外设都注册了相应的中断处理程序,如果其中一个在申请中断时使用了IRQF_NO_SUSPEND标志,那么当系统挂起时(在suspend_device_irqs之后,按理说每个IRQ都有被禁用),IRQ上所有设备的中断处理程序都可以正常触发,即使某些设备在调用request_irq(或其他中断注册函数)时没有设置IRQF_NO_SUSPEND标志。正因为如此,我们应该尽量避免同时使用IRQF_NO_SUSPEND和IRQF_SHARED这两个标志。3、系统中断唤醒接口:enable_irq_wake()和disable_irq_wake()有些中断可以将系统从休眠状态唤醒,我们称之为“能唤醒系统的中断”,当然是“能唤醒的中断”系统”需要配置启动唤醒系统这样的功能。此类中断在工作状态下一般表现为普通的I/O中断。只有当唤醒系统功能准备好启用时,才会启动一些特殊的配置和设置。这样的配置和设置可能与硬件系统(如SOC)上的信号处理逻辑有关,我们可以考虑如下HW框图:外设的中断信号发送到“通用中断信号处理模块”和“具体中断信号接收模块”。正常工作时,我们会开启“通用中断信号处理模块”的处理逻辑,关闭“特定中断信号接收模块”的处理逻辑。但是,当系统进入休眠状态时,有可能“通用中断信号处理模块”已经关闭。这时,我们需要启动“特定中断信号接收模块”来接收中断信号,使系统suspend-resume模块(往往是唯一能工作在suspend状态的HW块),才能正常工作由中断信号唤醒。一旦被唤醒,我们最好关闭“特定中断信号接收模块”,这样外设的中断处理就可以恢复到正常的工作模式,同时避免对系统suspend-resume模块造成不必要的干扰。IRQ子系统提供了两个接口函数来完成这个功能:enable_irq_wake()函数用来打开外设中断线到系统电源管理模块(也就是上面的suspend-resume模块),另一个接口是disable_irq_wake(),用于关闭从外设中断线到系统电源管理模块路径上的各种HW块。系统挂起时调用enable_irq_wake会影响suspend_device_irqs的处理,代码如下:;Disableinterruptcode}也就是说,一旦调用enable_irq_wake将设备的中断设置为系统suspend的唤醒源,peripheral中的中断不会被disable,而是会被打上IRQD_WAKEUP_ARMED标志。对于那些不是唤醒源的中断,suspend_device_irq函数会标记IRQS_SUSPENDED并禁用设备的irq。在系统唤醒过程中(resume_device_irqs),禁用的中断将被重新启用。当然,如果在suspend过程中发生了某些事件(比如wakeupsource产生了有效信号),导致了这个suspendabort,那么abort事件也会通知到PM核心模块。该事件不需要立即通知PM核心模块。一般来说,挂起线程会在某个时刻检查挂起的唤醒事件。在系统挂起过程中,唤醒源的每次中断都会终止挂起过程或唤醒系统(如果系统已经进入挂起状态)。但是suspend_device_irqs执行后,普通中断被屏蔽了。此时即使HW触发了中断信号,其中断处理程序也无法执行。IRQ作为唤醒源怎么样?虽然它的中断没有被屏蔽,但是它的中断处理程序是不会被执行的(此时的HWSignal只是用来唤醒系统)。唯一有机会执行的中断处理程序是那些标有IRQF_NO_SUSPEND标志的IRQ,因为它们的中断始终处于启用状态。当然,这些中断不应该调用enable_irq_wake来设置唤醒源。4.InterruptsandSuspend-to-IdleSuspend-to-idle(也称为“冻结”状态)是一种比较新的系统电源管理状态,相关代码如下:staticintsuspend_enter(suspend_state_tstate,bool*wakeup){...每个设备的latesuspend阶段每个设备的noirqsuspend阶段if(state==PM_SUSPEND_FREEZE){freeze_enter();gotoPlatform_wake;}...}Freeze和suspend之前的操作基本相同:先冻结进程中的进程system,然后suspendsystem程序中的各种设备是不一样的。noirqsuspend完成后,freeze不会禁用那些非BSP处理器和syscoresuspend阶段,而是调用freeze_enter函数将所有处理器推送到idle状态。此时,任何使能中断都可以唤醒系统。而这意味着那些被标记为IRQF_NO_SUSPEND的(其IRQ在suspend_device_irqs过程中没有被屏蔽)能够将处理器从空闲状态唤醒(但是需要注意的是这个信号不会触发系统唤醒信号),而普通中断无法唤醒处于空闲状态的处理器,因为它的IRQ被禁用。那些可以唤醒系统的唤醒中断呢?由于中断未被屏蔽,它还可以将系统从挂起到空闲状态唤醒。整个过程与将系统从suspend状态唤醒是一样的,唯一不同的是:将系统从freeze状态唤醒的中断处理路径,和从suspend状态唤醒系统的wakeup处理路径需要一个特殊的电源管理HWBLOCK参与中断处理逻辑。5、IRQF_NO_SUSPEND标志和enable_irq_wake函数不能同时使用。对于一个设备来说,在申请中断时使用IRQF_NO_SUSPEND标志,同时调用enable_irq_wake设置唤醒源是不合理的。主要原因如下:1.如果IRQ不是共享的,使用IRQF_NO_SUSPEND标志说明在整个系统的suspend-resume过程中(包括suspend_device_irqs之后的阶段)要保持中断打开,使其中断handler可以正常调用。调用enable_irq_wake函数表示要将设备的irq信号设置为中断源,所以不希望调用它的中断处理程序。这两个要求显然是相互排斥的。2.IRQF_NO_SUSPEND标志和enable_irq_wake函数都不是针对中断处理程序,而是针对IRQ上所有已注册的处理程序。在一个IRQ上共享唤醒源并且没有挂起中断源是荒谬的。然而,在非常特殊的情况下,可以将IRQ设置为唤醒源并设置IRQF_NO_SUSPEND标志。为了使代码逻辑正确,设备的驱动程序代码需要满足一些特殊要求。参考1.内核文件power/suspend-and-interrupts.txt