# ARM 上的通用中斷控制器: GICv3/4
:::warning
由於 security 與 virtualization 非作者主要關注,本文目前未詳細說明相關部分。
:::
## Brief history
GIC(Generic Interrupt Controller) 是在 ARM 平台上的通用中斷控制器。可管理中斷、排定中斷的優先順序及路由(routing)等與中斷相關的工作。架構上,目前有 V1 至 V4 的 4 種版本。而使用 ARM 來生產 SOC 廠商可以直接向 ARM 公司購買符合架構規範的 GIC 具體硬體實現,例如 GIC-400、GIC-500、GIC-600 等[型號](https://www.arm.com/zh-TW/products/silicon-ip-system/system-controllers/gic)。
從 GIC v2 到 v3 是一個巨大的分水嶺。與前兩代相比,GICv3 實作了需多新的功能。例如能夠支援更多 CPU、更多的中斷 id、message-based 的中斷等等。而 GICv4 與 v3 相比,主要只是增加在虛擬化功能的支援。因此,本文將以 GICv3 為主要對象,深入探討相關的功能與使用方式。
## Overview of GICv3
GICv3 主要應用於基於 ARMv8 設計的 SOC,例如 ARM Cortex-A53, ARM® Cortex-A57 and ARM Cortex-A72 等 CPU,而 GIC-500 或更新的 GIC 型號則能夠符合 GICv3 的規範。

> [How is the GICv3 registers context managed during system suspend state?](https://developer.arm.com/documentation/ka005993/1-0)
### PE
在 ARM 架構的設計上,以[處理元件(Processing element, 以下簡稱 PE)](https://developer.arm.com/documentation/102404/0202/Common-architecture-terms) 為術語來表示具有 Program Counter(PC) 並可以執行程式的任何單元。例如:
- Cortex-A8 是單核心、單執行緒處理器。整個處理器就是一個PE。
- Cortex-A53 是一個多核心處理器,每個核心都是單執行緒。每個核心都是一個 PE。
- Cortex-A65AE是一款多核心處理器,每個核心有兩個 thread。則每個 thread 都是一個 PE。
## GICv3 fundamentals
### GICv3 的中斷類型
在 GICv3 中,中斷可以分為以下幾種類型:
* SPI (Shared Peripheral Interrupt): 這是一種全域週邊中斷,可以 route 到指定的 PE,或路由到一組 PE
* PPI(Private Peripheral Interrupt: 針對單一特定的 PE 發送
* SGI(Software generated interrupt): 可以通過寫入 GIC 的 SGI 暫存器來觸發軟體中斷,通常此種中斷應用在處理器內部的交互中(inter-processor communication)
* LPI(Locality-specific Peripheral Interrupt): LPI 是在 v3 中新增的中斷類型。具體來說,LPI 以基於訊息的方式中斷,並且它們的配置保存在記憶體而不是寄存器,以換取在特定情形下的效能優勢。詳細會在後面章節說明。
根據中斷類型不同,它們的 interrupt ID(INTID) 在 ARM 架構中規範為以下表格所示:

硬體層面上,在 GICv3 的架構下,中斷的觸發可以藉由外部的訊號(例如 GPIO)通知中斷控制器,中斷控制器再根據設定將中斷分配給正確的 PE。如下圖所示:

而相比於前面版本,GICv3 還支援基於訊息的中斷。中斷的方式是藉由寫入中斷控制器中的暫存器,來設定或清除中斷。這種機制的優勢是: 每個中斷來源不再需要各自對應的專用訊號,換言之,硬體的設計上使用的 pin 可以大幅減少。

在 GICv3 中,SPI 可以是基於訊息的中斷,但 LPI 只能是基於訊息的中斷。
### Interrupt State Machine
中斷控制器為每個 SPI、PPI 和 SGI 中斷維護一個狀態機,後者由四種狀態組成:

- Inactive: 中斷尚未被 assert
- Pending: 中斷已 assert,但尚未 PE 所確認(Ack)
- Active: 中斷已 assert,且已被 PE 所確認
- Active and Pending: 中斷已被確認的同時,另一個相同來源的中斷(但發生在下個時間點)處於待處理狀態
:::info
LPI 中不存在這些狀態的概念
:::
而中斷的生命週期(何時 active)取決於它是 Edge-triggered 或者 Level sensitive。
#### Edge-triggered
在中斷訊號的 rising edge 時 assert,並持續直到根據 GIC 定義的方式清除狀態。

#### Level sensitive
當 interrupt 訊號 active,則 interrupt 被判定為 assert,直到訊號非 active 時,判定為未發生。

### Affinity routing
GICv3 支援 Affinity routing,以識別能將中斷路由到哪些特定的 PE 或 PE 組。以四個 8 位元的欄位來表示 PE 的 affinity(定義在 `MPIDR_EL1` 暫存器中)。
```
<aff level 3>.<aff level 2>.<aff level 1>.<aff level 0>
```

在 level 0 處有一個 Redistributor,負責控制 SGI、PPI、LPI 在對應 CPU Interface 上的管理(後續章節說明)。而不同 affinity level 的確切意義可以處理器和 SoC 廠商自行定義。例如:
* `<group of groups>. <group of processors>.<processor>.<core>`
* `<group of processors>.<processor>.<core>.<thread>`
### Security model
GICv3 支援 [ARM TrustZone 技術](https://www.arm.com/zh-TW/technologies/trustzone-for-cortex-a)。每個 INTID 都必須對應到一個 "Group" 和安全性設定。具體來說,有三種選擇。

根據選擇,在不同 EL 層級使用的中斷機制(IRQ/FIQ)也會有所差異。

下圖展示一種 routing 的模型: 指定 IRQ 在 EL1(`SCR_EL3.IRQ==0`) 而 FIQ 在 EL3 處理(`SCR_EL3.FIQ==1`)。則根據上表:
* 在 secure EL0 下:
* Secure Group 1 中是 IRQ,在 EL1 處理
* Non-Secure Group 1 是 FIQ,在 EL3 處理
* Group 0 永遠是 FIQ,在 EL3 處理
* 在 non-secure EL0 下:
* Secure Group 1 中是 FIQ,在 EL3 處理
* Non-Secure Group 1 是 IRQ,在 EL1 處理
* Group 0 永遠是 FIQ,在 EL3 處理

### Programmers’ model

在 GICv3 中斷控制器中,暫存器可以分三個部份的組件:
- Distributor interface
- Redistributor interface
- CPU interface
#### Distributor`(GICD_*)`
Distributor 暫存器是記憶體映射的,包含影響所有 PE 的全域設定,提供了以下介面:
* 中斷優先權和 SPI 分配
* 啟用和停用 SPI
* 設定每個 SPI 的優先權
* 每個 SPI 的路由資訊
* 將每個 SPI 設定為 edge-trigger 或 level sensitive
* 產生 message-based 的 SPI
* 控制 SPI active/pending 狀態
* 控制 security 相關的設定
#### Redistributors `(GICR_*)`
每個 PE 都有一個 Redistributors。Redistributors 提供的介面有:
* 啟用和停用 SGI 和 PPI
* 設定 SGI 和 PPI 的優先權
* 將每個 PPI 設定為 edge-trigger 或 level sensitive
* 將每個 SGI 和 PPI 指派給一個中斷群組
* 控制 SGI 和 PPI 的狀態
* LPI 相關的中斷屬性、待處理狀態在記憶體中資料結構的 base address 設定
* 對 PE 提供電源管理支援
#### CPU interfaces `(ICC_*_ELn)`
每個 Redistributor 都連接到一個 CPU interface。 CPU interface 提供以下介面:
* 實現中斷處理的通用控制和配置
* 中斷的確認(Ack)
* 執行優先權降低和中斷停用
* 為 PE 設定中斷優先權的 mask
* 定義 PE 的中斷搶佔策略
* 決定 PE 的待處理中斷中之最高優先權者
:::info
這些暫存器對應到 GICv1/GICv2 的 `GICC_*` 系列
:::
## Interrupt Handling
當中斷變成 pending 狀態時,GIC 決定是否發送中斷到被連接的 PE 中的其一。選擇的 PE 的取決於以下:
* Group: 每個 INTID 都屬於一個 Group(Group 0, Secure Group 1 或 Non-secure Group 1)。可以在 Distributor 或 CPU Interface 階層禁用某個 Group 的中斷
* Interrupt enables: 中斷可以被單獨禁用,則該中斷會保持在 pending 狀態,但不會被轉送到 PE。
* Routing controls:
* SPI: 由 `GICD_IROUTERn` 控制
* LPI: 藉由 ITS 決定配發的 PE
* PPI: 天生就只能轉送到特定的一個 PE
* SGI: 源頭的 PE 可以定義目標 PE 的清單
* Interrupt priority: 每個 PE 在其 CPU interface 中都有一個 Priority Mask register `ICC_PMR_EL1`,可設定轉送中斷到此 PE 所需的最低優先權
* Running priority: 如果 PE 不是在處理中斷,它的 Running priority 是 `0xFF`。而中斷只有在 priority 高於 running priority 的時候,可以搶佔當前中斷。
### Interrupt Acknowledge
CPU interface 有兩個 IAR 暫存器 `ICC_IAR0_EL1`、`ICC_IAR1_EL1`,分別用來讀取 Group0 和 Group1 的中斷。讀取 IAR 能夠得到 `INTID` 並推進中斷狀態機。
可能得到的 `INTID` 其中 1020、1021、1022、1023 這四個編號被保留來表示特殊的中斷狀況:
* 1020: 只能從 `ICC_IAR0_EL1` 讀到。當針對 Trusted OS 的中斷發生在 PE 執行於 non-secure state 時會讀到
* 1021: 只能從 `ICC_IAR0_EL1` 讀到。當針對 Rich OS 的中斷發生在 PE 執行於 secure state 時會讀到
* 1022: 用來相容 legacy
* 1023: Spurious interrupt,在沒有 pending 的中斷時嘗試讀取此暫存器會得到此值
以一個行動系統使用 modem 的中斷來發出來電訊號的案例說明: 此中斷是一個 non-secure group 1 的中斷,需要由 non-secure state 下的 Rich OS 處理。假設 rounting 設定如下:

1. 當 PE 在 secure EL1 上執行 Trusted OS 時發生 modem 中斷。由於後者預期是 non-secure group 1 的中斷,因此將以 FIQ 形式發出訊號,並被帶到 EL3。
2. 在 EL3 上執行的 secure monitor 讀取 IAR 後會得到 1021,表示中斷預計在 non secure state 下處理,secure monitor 因此做相關切換。
3. PE 現在處於 non secure state,中斷以 IRQ 形式發出並進入 non secure EL1,這時就可由 Rich OS 處理之。
也可以有其他的處理模型,詳見 [GICv3 and GICv4 Software Overview](https://developer.arm.com/documentation/dai0492/latest/) 5.3 章節。
### Running priority & preemption
Priority Mask register(PMR) 設定了將中斷轉送至特定 PE 所必須具有的最低優先級。而 GICv3 架構還有 "Running priority" 的概念: 當 PE ack 中斷時,其 Running priority 變成中斷的 priority。當 PE 寫入 EOI(end of interrupt) 暫存器時,Running priority 返回到前一個數值。
Running priority 對於搶佔時的優先順序很重要。當高優先權中斷發生,而 PE 在比較低優先的 running priority 時,搶佔就會發生。
目前的 running priority 可以在 CPU interface 的 `ICC_RPR_EL1` 暫存器中得知。


如上圖描述了允許與不允許搶佔時的 running priority 變化。
不想要搶佔發生的情況下,在 GICv3 可以透過暫存器 `ICC_BPRn_EL1` 來控制相關設定。暫存器的欄位設計如下:

對於搶占只需要考慮 Group 即可。具體案例中,考慮以下三個中斷:
* INTID A 的優先權為 0x10
* INTID B 的優先權為 0x20
* INTID C 的優先權為 0x21
在這種情況下,可以決定:
* A 可以搶佔 B 或 C
* B 不能搶佔 C,因為 B 和 C 的優先權相似
### End of Interrupt
軟體必須通知 GIC 中斷已處理完成,以便 state machine 可以轉換到下一個狀態。在 GICv3 架構將此視為兩個階段:
* Priority Drop: 將 running prioriy 復原到中斷發生之前的值
* Deactivation: 更新目前正在處理之中斷的 state machine,通常是從 active 轉換為 deactive 狀態
取決於 `ICC_CTLR_ELn.EOImode` 的設定,這兩步驟可以同時或者獨立發生:
* EOImode = 0: 寫入 `ICC_EOIR0_EL1` (Group0)或 `ICC_EOIR1_EL1`(Group 1) 會同時執行 Priority Drop 和 `Deactivation`
* EOImode = 1: 寫入 `ICC_EOIR0_EL1` (Group0)或 `ICC_EOIR1_EL1`(Group 1) 會導致 Priority Drop。Deactivation 則需要另外寫入 `ICC_DIR_EL1`。通常用於虛擬化目的。
### Interrupt State
如果想確認 PE 目前最高優先級的 pending interrupt,能存取暫存器 `ICC_HPPIR0_EL1` 和 `ICC_HPPIR1_EL1` 對應中斷的 INTID。對於 PE 當前的 running priority 則可以讀取 `ICC_RPR_EL1` 來得知。
Distributor 上的暫存器可以提供每個 SPI 的目前狀態。而 Redistributor 的暫存器則可提供所連接之 PE 的 PPI 和 SGI 狀態。
可以參考以下表格:

## LPI 的使用說明
LPI 是一種 message-based 的中斷,屬於可選的中斷類型(透過 `GICD_TYPER.LPIS` 可確認),且只有在 Affinity routing 啟用時才支援。與其他中斷類型的設定方式不同,LPI 的使用涉及以下設定:
* Redistributors
* ITS(Interrupt Translation Service): 非必要,可選
ITS 的功能是接收來自週邊裝置的中斷,並將其以 LPI 形式轉發到相應的 Redistributor。一個系統可能包含多個 ITS,在這種情況下,每個 ITS 都必須單獨配置。
也可以選擇繞過 ITS 直接將 LPI 傳送到 Redistributor。但 ITS 提供了許多功能,可以有效處理大量中斷來源。
### ITS
週邊裝置向 ITS 發送以下兩項資訊來產生 LPI:
* EventID: 寫入 `GITS_TRANSLATER` 標識裝置正在發送的中斷種類。EventID 可以與 INTID 相同,也可以由 ITS 轉換為 INTID。
* DeviceID: DeviceID 用來識別週邊裝置。DeviceID 的產生由實作定義。例如 AXI user signals。
LPI INTID 被分組到各個 *collections* 中,其中的所有 INTID 都會 route 到同一個 Redistributor。軟體藉由將 LPI INTID 分配給 *collections*,從而能夠有效地將中斷從一個 PE 移動到另一個 PE。

ITS 使用三種類型的表來處理 LPI 的轉換與 routing。
* Device Table: 將 DeivceID 對應到 Interrupt Translation Tables
* Interrupt Translation Tables: 包含 EventID 和 INTID 之間特定於 DeviceID 的對應。還包含 INTID 所屬的 collection 的資訊。
* Collection Table: collections 到 Redistributor 的對應
當週邊寫入 `GITS_TRANSLATER` 時,ITS 會進行下列步驟:
1. 透過 DeviceID 從 Device Table 中選擇對應 entry 以找到正確的 Interrupt Translation Table
2. 使用 EventID 從 Interrupt Translation Table 中選擇對應 entry。由此得到 INTID 和 Collection ID。
3. 使用 Collection ID 在 Collection Table 中選擇對應 entry,以得知 routing 資訊。
4. 藉由 routing 將中斷轉發至目標 Redistributor。
ITS 的具體實作和設定細節請參考 [GICv3 and GICv4 Software Overview Chapter 6.1](https://developer.arm.com/documentation/dai0492/latest/)。
### Redistributor
Redistributor 可以使用記憶體中的表來處理 LPI 的控制、優先權和 pending 資訊。

暫存器 `GICR_PROPBASER` 會指向記憶體中 LPI Configuration tables,後者描述 LPI 設定的相關資訊。LPI 的設定是全域的。換言之,一個系統只有一個 LPI 配置表,由所有 Redistributor 共用。
類似的,LPI 的狀態資訊也儲存在記憶體中的 LPI Pending tables,由 `GICR_PENDBASER` 指向。每個 Redistributor 都有自己的 LPI Pending tables。
Redistributor 的具體實作和設定細節請參考 [GICv3 and GICv4 Software Overview Chapter 6.2](https://developer.arm.com/documentation/dai0492/latest/)。
## Reference
* [GICv3 and GICv4 Software Overview](https://developer.arm.com/documentation/dai0492/latest/)
* [Arm Generic Interrupt Controller (GIC) Architecture Specification](https://developer.arm.com/documentation/ihi0069/latest/)
* [linux kernel的中断子系统之(七):GIC代码分析](http://www.wowotech.net/irq_subsystem/gic_driver.html)
* [LPI pending table]( https://patchwork.kernel.org/project/xen-devel/patch/20170130183153.28566-4-andre.przywara@arm.com/)