系統掛起和裝置中斷

版權 (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 無效。