Try   HackMD

Arm Programmer's Guide XII - Interrupt Handling 學習筆記

1. 前言

此筆記為學習 ARM® Cortex™-A Series Programmer's Guide
Version: 4.0 中第十二章 Interrupt Handling
的心得筆記。主旨在解釋 Armv7 Aarch32 下關於中斷的行為跟設計。這篇的內容和前一篇 Exception Handling 有許多重疊之處,因為中斷對於系統來說就是一種非同步異常,所以此筆記主要都會紀錄和前篇有所不同的內容。

2. 概論

由於現在核心都會接著許多外部設備,而他們跟核心溝通的其中一個重要方式就是中斷,所以核心會需要處理來自大量來源的中斷。為了有個辦法能對這些中斷源做管理,GIC (Generic Interrupt Controller) 誕生了。他是一個 Memory-mapped 的硬體設備,位於所有中斷源和核心之間。所有中斷訊號實際上都是先接到 GIC,讓 GIC 去分配各中斷的方向 (要給哪個核心)、優先權或開關等等。(這邊提到的 GIC 版本為 V2。)

3. 外部中斷要求 (External Interrupt Request)

有兩種外部中斷要求 FIQ (Fast Interrupt Request) 和 IRQ (Interrupt Request),對於核心來說,他們都是低電位觸發 (level-sensitive active-low, 也就是當該中斷訊號源的電壓為低就能於核心中觸發中斷。相對於 Level-sensitive 的是邊緣觸發 Edge-triggered,也就是當電壓有變化時才能觸發中斷)。FIQ 顧名思義是較快速、擁有較短反應時間的中斷 (實際原因和設計實作可以參考前篇第 3.1 章 Interrupts)。

3.1 中斷的開關

一個核心要能成功的接收中斷並發起中斷異常的話,他必須要將 CPSR (Current Program Status Register) 的 I (針對 IRQ) 和 F (針對 FIQ) BIT 清除,才能夠辦到。 Arm 提供以下兩個指令來快速執行 CPSR 的 I、F 甚至是 A (針對非同步 Abort) 的開關。

  1. CPSIE (Current Program Status Interrupt Enable)。後面參數至少是一個 I、F 和 A。舉例:
    ​​​​CPSIE IFA   // Disable IRQ、FIQ and Asynchronous abort
    ​​​​CPSIE I     // Disable IRQ
    
  2. CPSID (Current Program Status Interrupt Disable)。後面參數一樣是至少一個 I、F 和 A。舉例:
    ​​​​CPSID F    // Enable FIRQ
    ​​​​CPSID IA   // Enable IRQ and Asynchronous abort
    

對於 Arm Cortex-A 系列來說,FIQ 是可以被設定成無法關閉的,這種 FIQ 被稱為無法屏蔽 FIQ (Non-Maskable FIQ)。設定方法是操控一根只有在核心 Reset 時才會去取樣的輸入訊號。但就算 FIQ 被設定成無法屏蔽的,當核心在正在處理 FIQ 異常時,還是會關掉 FIQ。

3.2 巢狀中斷處理 (Nested Interrupt Handling)

相對於巢狀中斷處理的是簡單中斷處理 (Simplistic Interrupt Handling),也就是中斷異常處理的當下是不允許相同的中斷異常再次發起的 (但可以允許不同的異常發起,像是 IRQ 異常處理中不允許再次發起 IRQ 異常,但是可以發起 FIQ 異常處理),其流程可以參考下圖和前一篇第六章 Exception Handling Flow

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

巢狀中斷處理允許中斷異常處理的當下,再次發起一樣的中斷異常 (舉例來說,系統允許第二個 IRQ 中斷異常,打斷當下的 IRQ 中斷異常處理),而為了達到這樣的目的,中斷異常處理的函式 (也就是 IRQ 或 FIQ 的 Exception Handler,或者可以稱他們為 ISR, Interrupt service routine) 必須要是可重複進入的 (Reentrant)。

如果一個函式是可重複進入的,那就表示就算該函式在執行途中被打斷,並且再次被呼叫重頭執行,也不會造成任何不預期的結果。

3.1.1 流程

以下為支援巢狀中斷的 ISR 應該有的流程 (這邊以 IRQ 舉例) (以下流程在順序上有調整空間,像第4 步也可以是第 2 步):

  1. 將 LR_irq 和 SPSR_irq (他們為上一層 ISR 或是被中斷打斷流程的 PC 和 CPSR) 推進 Stack_irq 中保存起來。否則新的 IRQ 中斷異常會改寫這兩個值,導致其資訊遺失。
  2. 將核心模式切成 SVC 模式。
  3. 將 SVC 的通用寄存器 (R0~R3, R12) 和 LR_svc (他們為上一層 ISR SVC 背景資訊) 保存於 Stack_svc 中。(若沒有 2 和 3 的步驟,那麼若 ISR 中有 BL 至其他函式,其返回地址 LR_irq 會在新的中斷異常發生時被改寫並遺失。這點會在稍後圖解第六步中有更多說明。)
  4. 確認中斷訊號源,並將其硬體訊號移除。否則會在稍後打開中斷後,馬上再觸發一樣的中斷訊號。
  5. 開啟 IRQ 中斷。
  6. 執行 ISR 本體。
  7. 關掉 IRQ 中斷。
  8. 從 Stack_svc 中恢復 SVC 的通用寄存器、LR_svc。也就是恢復上一層 ISR 的 SVC 背景狀態。
  9. 切回 IRQ 模式。
  10. 從 Stack_irq 中恢復 PC 和 CPSR。

3.1.2 實作範例

以下為一個支援巢狀中斷流程、可重複進入的 ISR:

IRQ_Handler: // Now in IRQ mode SUB lr, lr, #4 SRS sp!, #0x12 // SRS (Store Return Status). 0x12 is IRQ mode's ID CPS #0x13 // Change to SVC mode. 0x13 is SVC mode's ID PUSH {r0-r3, r12} // Keep SVC's context in Stack_svc AND r1, sp, #4 SUB sp, sp, r1 PUSH {r1, lr} BL identify_and_clear_source CPSIE I // Enable IRQ in CPSR BL C_irq_handler // Execute ISR CPSID i // Disable IRQ in CPSR POP {r1, lr} // Restore SVC's context from Stack_svc ADD sp, sp, r1 POP {r0-r3, r12} CPS #0x12 // Change to IRQ mode RFE sp! // Return From Exception

第 3、4 行中將 LR 減四後才存入 Stack 中是為了在最後拿 LR 還原 PC 時,能跳回 IRQ 發生當下指令的下一個指令。更多細節可以參考上一篇第六章 Exception Handling flow 的第一個表格。

3.1.3 圖解

這邊圖解當 ISR 中又發生 IRQ 中斷異常打斷當下 ISR 的流程:

  1. 於正常流程中發生 IRQ,PC 跳至 IRQ Handler 後,第一步驟就是保存原本流程的相關資訊。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  2. 切至 SVC 模式,並把 SVC 當下的寄存器包含 LR 存進 Stack_svc 中。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  3. 打開 IRQ 中斷。

  4. 呼叫 foo(),但是跳去 foo 後馬上發生另一個 IRQ 中斷。這時 LR_svc 會指向 BL foo 的位置、PC_SVC 會留在 foo() 的一開始。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  5. 再次進到 IRQ_Handler 中,重複步驟一將 LR、SPSR 推進 Stack_irq 中。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  6. 重複步驟二,再次切到 SVC 模式並把當下的寄存器包和 LR 存進 Stack_svc 中。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    上方紅虛框內是關鍵。如果沒有切到 SVC mode,而試著在 IRQ 中完成整個 Reentrant ISR,那這時會發現上一層 ISR 的 LR 資訊 (也就是這個例子中的 LR_svc 值) 遺失了,因為 LR_irq 會在新的 IRQ 中斷異常發生時自動被蓋成上一層的 PC。
    另一種只靠 IRQ 實作的可能想法,是在 ISR 呼叫的函式一開始將 LR 推進 stack 中保存避免遺失 如同步驟四中的 foo() 所為 但也如同步驟四的圖所示,IRQ 有可能發生在已經跳至該函式,但是執行 PUSH {LR} 之前。

  7. 重複步驟三,打開 IRQ。

  8. 呼叫 foo()、foo2() 並完整跑完,最後 LR_svc 會指向 BL foo2 的位置。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  9. 從 SVC STACK 中取回上一層 IRQ 中的 SVC LR 和其他寄存器、從 IRQ STACK 中取回上一層 IRQ 的 PC,最後回到第一層 ISR 發生 IRQ 位置的下一個指令。

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  10. 接著繼續執行直到 ISR 完成。

4. GIC, Gerneric Interrupt Controller

GIC 是一個用來管理所有中斷源的 Memory-Mapped 裝置,以軟體的角度來說,他是由 Distributor 和 CPU Interface 組成,他們的功能為:

  • Distributor (能夠被所有的核心共享存取)
    1. 設定各中斷的狀態。
    2. 設定各中斷是否開啟。

      相對於 CPU Interface 中的屏蔽,這是一個全局的開關。若關閉,則沒有任何核心能收到此中斷。

    3. 設定各中斷為邊緣觸發 (Edge-Triggered) 或是電位觸發 (Level-Sensitive)。

      這邊乍看之下可能會和第三章開頭提到的 IRQ 和 FIQ 都是低電位觸發有矛盾,但兩者說的中斷觸發位置其實並不一樣,第三章開頭講的是對於核心來說,用來觸發 FIQ 和 IRQ 的 Input Pin 是會被低電位給觸發中斷;而這邊提到的是 GIC 可以透過寄存器的設定來選擇中斷源要如何在 GIC 中引起中斷。

    4. 設定各中斷源的優先權。
    5. 設定各中斷要指派給哪個 CPU Interface。
    6. 設定各中斷屬於安全還是非安全模式 (Secure or Normal World)。
    7. 產生軟體中斷 (SGI, Software Generated Interrupt)。
  • CPU Interface (每個核心都擁有一個,且各核心透過一樣的記憶體位置來存取他們各自的 CPU Interface)
    1. 控制各個到該核心的中斷要被屏蔽與否。

      相對於 Distributor 中的開關,這邊的屏蔽只針對特定的核心,且屏蔽並不是針對特定的中斷,而是透過優先等級來屏蔽,也就是說,若某中斷的優先權低於其指定核心 CPU Interface 中的屏蔽優先權,那麼 Distributor 便不會將該中斷指派過去,該中斷會維持在 Pending 狀態。

    2. 提供 IAR (Interrupt Acknowledge Register) 寄存器讓核心讀取以得知他正在處理的中斷 ID。
    3. 提供 EOIR (End of Interrupt Register) 寄存器讓核心通知 GIC 中斷已經處理完畢。

Distributor 和 CPU Interface 在 Reset 之後都會回到被關閉的狀態,軟體需要將他們依序初始化並打開,最後連同核心 CPSR 的 I、F BIT 都確定清除後,核心才能開始接收中斷。

4.1 中斷類型

在 GIC 中,中斷都擁有一個獨一無二、用來辨別的 Interrupt ID,軟體就是用此 ID 來定位特定的中斷。而根據 Interrupt ID 值的區間,可以將中斷分為四種類型:

  1. Interrupt ID 0 ~ 15: 軟體中斷 (SGI, Software Generated Interrupt)
    軟體中斷並不是由硬體透過實際的電路所引起,而是透過寫 Distributor 的 ICDSGIR (Software Generated Interrupt Register) 寄存器所觸發。很常使用於核心間的溝通。
  2. Interrupt ID 16 ~ 31: 私有中斷 (PPI, Private Peripheral Interrupt)
    PPI 是來自屬於特定核心的私有周邊。所以對於不同核心來說,擁有相同 ID 的 PPI ,其硬體來源其實是不一樣的 舉例來說,各核心的 Timer。
  3. Interrupt ID 32 ~ 1020: 共享中斷 (SPI, Share Peripgeral Interrupt)
    由硬體周邊觸發,且能夠被 GIC 自由地分配給一個或多個核心。
  4. Interrupt ID 1023: 偽中斷 (Spurious Interrupt)
    此為特別保留的 ID 號碼,無法改給其他中斷使用。若中斷 ID 寄存器讀到此值 1023,則示沒有待處理的中斷。

4.2 中斷狀態 (State)

中斷的狀態存於 Distributor 中,其可能的狀態如下:

  1. Inactive: 中斷未被發起。
  2. Pending: 中斷已被發起,但還在等待 Distributor 將它指派給某個 CPU Interface。
  3. Active: 該中斷已被指派給核心。
  4. Active and Pending: 核心已經在處理該中斷,但是又有一個同樣的中斷處於 Pending 狀態。

4.3 中斷發起流程

  1. 中斷被發起後,首先會從 Inactive 進到 Pending (或者從 Active 進到 Active and Pending) 狀態。
  2. Distributor 會根據其中的優先權還有中斷跟核心間的指派資訊,從所有的 Pending 中斷中挑出一個最高優先權的中斷並將他指派給指定的 CPU Interface
  3. CPU Interface 會依序、且一次一個地將其收到的中斷通知給核心,並發起 IRQ 或者 FIQ 異常。
  4. Interrupt Exception Handler (或者稱為 ISR) 透過讀取 CPU Interface 中的 IACR 寄存器 (讀取動作會讓該中斷狀態變成 Active) 得知當前的 Interrupt ID、來自哪一個中斷源,並做相對應的異常處理。
  5. 中斷異常處理完後,ISR 將處理完的 Interrupt ID 寫入 EOIR 寄存器來通知 CPU Interface 中斷異常已完成 (中斷進入 Inactive 狀態、或者從 Active and Pending 進入 Pending 狀態)。
  6. CPU Interface 在收到異常處理完成的通知後,他就會再將 Distributor 指派給他下一個中斷通知給核心。

上一篇: Arm Programmer's Guide XI - Exception Handling 學習筆記
下一篇: