系统挂起和设备中断

版权 (C) 2014 Intel 公司。作者: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() 之后,将像往常一样执行为其安装的所有中断处理程序,即使 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 执行,但是这些 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 用户之间共享。 为了保证安全,唤醒设备驱动程序必须能够区分来自真正唤醒事件的虚假 IRQ(使用 pm_system_wakeup() 向核心发出后者的信号),必须使用 enable_irq_wake() 来确保 IRQ 将充当唤醒源,并且必须使用 IRQF_COND_SUSPEND 请求 IRQ,以告诉核心它符合这些要求。 如果不满足这些要求,则使用 IRQF_COND_SUSPEND 无效。