系统挂起和设备中断

版权 (C) 2014 Intel Corp. 作者:Rafael J. Wysocki <rafael.j.wysocki@intel.com>

挂起和恢复设备 IRQ

设备中断请求线 (IRQ) 通常在系统挂起期间,在挂起设备的“晚期”阶段(即,在所有设备的 ->prepare、->suspend 和 ->suspend_late 回调都已执行之后)被禁用。这是通过 suspend_device_irqs() 完成的。

这样做的理由是,在设备挂起的“晚期”阶段之后,没有合法的理由可以触发来自挂起设备的任何中断,并且如果任何设备尚未正确挂起,最好还是阻止它们的中断。此外,过去我们在共享 IRQ 的中断处理程序方面遇到了问题,实现这些处理程序的设备驱动程序没有为设备挂起后触发的中断做好准备。在某些情况下,它们会尝试访问例如挂起设备的内存地址空间,并导致出现不可预测的行为。不幸的是,此类问题很难调试,而引入 suspend_device_irqs() 以及设备挂起和恢复的“noirq”阶段是缓解这些问题的唯一可行方法。

设备 IRQ 在系统恢复期间重新启用,正好在设备恢复的“早期”阶段之前(即,在开始执行设备的 ->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 是共享的,那么为它安装的所有中断处理程序都将在 suspend_device_irqs() 之后照常执行,即使某些 IRQ 用户没有将 IRQF_NO_SUSPEND 标志传递给 request_irq()(或等效的)。因此,应避免同时使用 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 执行,但不应使用 enable_irq_wake() 为系统唤醒配置这些 IRQ。

中断和挂起到空闲

挂起到空闲(也称为“冻结”睡眠状态)是一种相对较新的系统睡眠状态,其工作方式是在挂起设备的“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 用户之间共享。为了确保安全,唤醒设备驱动程序必须能够区分虚假 IRQ 和真正的唤醒事件(使用 pm_system_wakeup() 向核心发出后者的信号),必须使用 enable_irq_wake() 以确保 IRQ 将作为唤醒源工作,并且必须使用 IRQF_COND_SUSPEND 请求 IRQ,以告知核心它满足这些要求。如果不满足这些要求,则使用 IRQF_COND_SUSPEND 无效。