系统挂起和设备中断
版权
© 2014 Intel Corp. 作者:Rafael J. Wysocki rafael.j.wysocki@intel.com
挂起和恢复设备中断
在系统挂起后(即在所有设备的->prepare、->suspend和->suspend_late回调已经执行完毕之后),通常会禁用设备中断请求线(IRQs)。这是通过suspend_device_irqs()来实现的。
这样做的理由是,在设备挂起的“晚期”阶段之后,暂停设备的任何中断触发都没有合法的理由,如果有任何设备尚未正确挂起,最好也阻止来自它们的中断。此外,在过去,我们曾遇到过共享IRQ的中断处理程序在设备挂起后未能准备好处理中断触发的问题。在某些情况下,它们会尝试访问已挂起设备的内存地址空间,导致结果不可预测的行为。不幸的是,这类问题非常难以调试,引入suspend_device_irqs()以及设备挂起和恢复的“noirq”阶段是缓解这些问题的唯一实际方法。
在系统恢复期间,设备中断会在“早期”阶段(即在开始执行设备的->resume_early回调之前)重新启用。执行此操作的函数是resume_device_irqs()。
IRQF_NO_SUSPEND标志
有些中断在整个系统挂起-恢复周期中都可以合法触发,包括设备挂起和恢复的“noirq”阶段,以及非引导CPU被下线和重新上线的时间。这主要适用于定时器中断,同时也适用于IPI和一些其他特殊用途的中断。
当请求特殊用途的中断时,IRQF_NO_SUSPEND标志用于向IRQ子系统指示这一点。它导致suspend_device_irqs()保持相应的IRQ启用,以便在挂起-恢复周期中使中断按预期工作,但并不保证中断会唤醒系统从挂起状态唤醒--对于这种情况,需要使用enable_irq_wake()。
请注意,IRQF_NO_SUSPEND标志影响整个IRQ,而不仅仅是它的一个用户。因此,如果IRQ是共享的,即使某些IRQ的用户没有将IRQF_NO_SUSPEND标志传递给request_irq()(或等效的函数),在suspend_device_irqs()之后仍将像往常一样执行为其安装的所有中断处理程序。因此,应避免同时使用IRQF_NO_SUSPEND和IRQF_SHARED。
系统唤醒中断、enable_irq_wake()和disable_irq_wake()
系统唤醒中断通常需要配置为从睡眠状态唤醒系统,特别是如果它们在工作状态中用于不同目的(例如作为I/O中断)。
这可能涉及在平台内部(如SoC)启用特殊的信号处理逻辑,以便在系统睡眠期间从给定线路接收的信号以不同的方式路由,以在需要时触发系统唤醒。例如,平台可能包括一个专用的中断控制器,专门用于处理系统唤醒事件。然后,如果给定的中断线路应该从睡眠状态唤醒系统,那么该中断控制器的相应输入需要被启用,以接收来自相关线路的信号。唤醒后,通常最好禁用该输入,以防止专用控制器不必要地触发中断。
IRQ子系统提供了两个辅助函数供设备驱动程序用于这些目的。即enable_irq_wake()用于启用平台处理给定IRQ作为系统唤醒中断线路的逻辑,而disable_irq_wake()用于关闭该逻辑。
调用enable_irq_wake()会导致suspend_device_irqs()以特殊方式处理给定的IRQ。即,IRQ保持启用,但在第一个中断时将被禁用,并标记为挂起,以便在随后的系统恢复期间由resume_device_irqs()重新启用。此外,PM核心会收到有关导致正在进行的系统挂起中止的事件的通知(这不一定会立即发生,而是在挂起线程寻找挂起唤醒事件的某个点上)。
这样,来自唤醒中断源的每个中断都会导致当前正在进行的系统挂起被中止,或者如果已经挂起,则唤醒系统。但是,在suspend_device_irqs()之后,系统唤醒IRQ的中断处理程序不会被执行。此时,它们只会为IRQF_NO_SUSPEND的IRQ执行,但这些IRQ不应使用enable_irq_wake()配置为系统唤醒。
中断和挂起到空闲状态
挂起到空闲状态(也称为“冻结”睡眠状态)是一种相对较新的系统睡眠状态,它通过使所有处理器处于空闲状态并在挂起设备的“noirq”阶段后等待中断来工作。
当然,这意味着所有设置了IRQF_NO_SUSPEND标志的中断都会在该状态下唤醒CPU,但它们不会导致IRQ子系统触发系统唤醒。
系统唤醒中断,反过来,将触发从挂起到空闲状态的唤醒,类似于它们在完全系统挂起情况下的作用。唯一的区别是,从挂起到空闲状态的唤醒是使用通常的工作状态中断传递机制进行信号传递的,并不需要平台使用任何特殊的中断处理逻辑来使其工作。
IRQF_NO_SUSPEND和enable_irq_wake()
几乎没有什么有效的理由同时在同一个IRQ上使用enable_irq_wake()和IRQF_NO_SUSPEND标志,而且在同一个设备上同时使用两者是无效的。
首先,如果IRQ不是共享的,处理IRQF_NO_SUSPEND中断的规则(在suspend_device_irqs()之后调用中断处理程序)与处理系统唤醒中断的规则(在suspend_device_irqs()之后不调用中断处理程序)直接相矛盾。
其次,enable_irq_wake()和IRQF_NO_SUSPEND都适用于整个IRQ,而不是单个中断处理程序,因此在系统唤醒中断源和IRQF_NO_SUSPEND中断源之间共享IRQ通常是没有意义的。
在极少数情况下,一个IRQ可以在唤醒设备驱动程序和使用IRQF_NO_SUSPEND的用户之间共享。为了确保安全,唤醒设备驱动程序必须能够区分虚假的中断和真正的唤醒事件(通过向核心发出pm_system_wakeup()信号),必须使用enable_irq_wake()来确保IRQ将作为唤醒源进行功能,并且必须使用IRQF_COND_SUSPEND请求IRQ以告知核心它满足这些要求。如果这些要求不满足,就不能使用IRQF_COND_SUSPEND。