萬字講解Linux中斷 —— ARM GIC 中斷控制器
GIC(Generic Interrupt Controller)是ARM公司提供的一個(gè)通用的中斷控制器,其architecture specification目前有四個(gè)版本,V1~V4(V2最多支持8個(gè)ARM core,V3/V4支持更多的ARM core,主要用于ARM64服務(wù)器系統(tǒng)結(jié)構(gòu))。目前在ARM官方網(wǎng)站只能下載到Version 2的GIC architecture specification,因此,本文主要描述符合V2規(guī)范的GIC硬件及其驅(qū)動(dòng)。
具體GIC硬件的實(shí)現(xiàn)形態(tài)有兩種,一種是在ARM vensor研發(fā)自己的SOC的時(shí)候,會(huì)向ARM公司購(gòu)買GIC的IP,這些IP包括的型號(hào)有:PL390,GIC-400,GIC-500。其中GIC-500最多支持128個(gè) cpu core,它要求ARM core必須是ARMV8指令集的(例如Cortex-A57),符合GIC architecture specification version 3。另外一種形態(tài)是ARM vensor直接購(gòu)買ARM公司的Cortex A9或者A15的IP,Cortex A9或者A15中會(huì)包括了GIC的實(shí)現(xiàn),當(dāng)然,這些實(shí)現(xiàn)也是符合GIC V2的規(guī)格。
1. ARM中斷控制器
1.1 ARM支持中斷類型ARM GIC-v2支持三種類型的中斷:
SGI:軟件觸發(fā)中斷(Software Generated Interrupt),通常用于多核間通訊,最多支持16個(gè)SGI中斷,硬件中斷號(hào)從ID0~ID15。SGI通常在Linux內(nèi)核中被用作 IPI 中斷(inter-processor interrupts),并會(huì)送達(dá)到系統(tǒng)指定的CPU上。
PPI:私有外設(shè)中斷(Private Peripheral Interrupt),是每個(gè)CPU私有的中斷。最多支持16個(gè)PPI中斷,硬件中斷號(hào)從ID16~ID31。PPI通常會(huì)送達(dá)到指定的CPU上,應(yīng)用場(chǎng)景有CPU本地時(shí)鐘。
SPI:公用外設(shè)中斷(Shared Peripheral Interrupt),最多可以支持988個(gè)外設(shè)中斷,硬件中斷號(hào)從ID32~ID1019。
1.2 GIC檢測(cè)中斷流程
GIC主要由兩部分組成,分別是仲裁單元(Distributor)和CPU接口(Interface)模塊

從圖中可以看出, GIC 可以清晰的劃分成兩個(gè)部分:仲裁( Distributor )和 CPU 接口(CPU Interface)。CPU interface有兩種,一種就是和普通processor接口,另外一種是和虛擬機(jī)接口的。Virtual CPU interface在本文中不會(huì)詳細(xì)描述。分發(fā)器其實(shí)應(yīng)該叫匯聚器,在 IC 的后端設(shè)計(jì)中, layout 會(huì)把各個(gè)模塊引過來的中斷線(就是上面說的三種中斷)混接到 GIC 上,然后把混聚合的中斷接到 CPU 的 IRQ 和 FIQ 線上,這樣 CPU 就有觸覺了。
其中 Distribute 只有一套,故基地址也只有一個(gè),而 Interface 有多套,因?yàn)槊總€(gè) CPU 都對(duì)應(yīng)一套 interface 。
其中各個(gè)子中斷使能,設(shè)置觸發(fā)方式,優(yōu)先級(jí)排序,分發(fā)到哪個(gè) CPU 上這些功能由 Distribute 負(fù)責(zé),總的中斷的使能,狀態(tài)的維護(hù)由 Interface 負(fù)責(zé)。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)?


Distributor
Distributor的主要的作用是檢測(cè)各個(gè)interrupt source的狀態(tài),控制各個(gè)interrupt source的行為,分發(fā)各個(gè)interrupt source產(chǎn)生的中斷事件分發(fā)到指定的一個(gè)或者多個(gè)CPU interface上。雖然Distributor可以管理多個(gè)interrupt source,但是它總是把優(yōu)先級(jí)最高的那個(gè)interrupt請(qǐng)求送往CPU interface。Distributor對(duì)中斷的控制包括:
(1)中斷enable或者disable的控制。Distributor對(duì)中斷的控制分成兩個(gè)級(jí)別。一個(gè)是全局中斷的控制(GIC_DIST_CTRL)。一旦disable了全局的中斷,那么任何的interrupt source產(chǎn)生的interrupt event都不會(huì)被傳遞到CPU interface。另外一個(gè)級(jí)別是對(duì)針對(duì)各個(gè)interrupt source進(jìn)行控制(GIC_DIST_ENABLE_CLEAR),disable某一個(gè)interrupt source會(huì)導(dǎo)致該interrupt event不會(huì)分發(fā)到CPU interface,但不影響其他interrupt source產(chǎn)生interrupt event的分發(fā)。
(2)控制將當(dāng)前優(yōu)先級(jí)最高的中斷事件分發(fā)到一個(gè)或者一組CPU interface。當(dāng)一個(gè)中斷事件分發(fā)到多個(gè)CPU interface的時(shí)候,GIC的內(nèi)部邏輯應(yīng)該保證只assert 一個(gè)CPU。
(3)優(yōu)先級(jí)控制。
(4)interrupt屬性設(shè)定。例如是level-sensitive還是edge-triggered
(5)interrupt group的設(shè)定
Distributor可以管理若干個(gè)interrupt source,這些interrupt source用ID來標(biāo)識(shí),我們稱之interrupt ID。
CPU interface
CPU interface這個(gè)block主要用于和process進(jìn)行接口。該block的主要功能包括:
(a)enable或者disable CPU interface向連接的CPU assert中斷事件。對(duì)于ARM,CPU interface block和CPU之間的中斷信號(hào)線是nIRQCPU和nFIQCPU。如果disable了中斷,那么即便是Distributor分發(fā)了一個(gè)中斷事件到CPU interface,但是也不會(huì)assert指定的nIRQ或者nFIQ通知processor。
(b)ackowledging中斷。processor會(huì)向CPU interface block應(yīng)答中斷,中斷一旦被應(yīng)答,Distributor就會(huì)把該中斷的狀態(tài)從pending狀態(tài)修改成active。如果沒有后續(xù)pending的中斷,那么CPU interface就會(huì)deassert nIRQCPU和nFIQCPU信號(hào)線。如果在這個(gè)過程中又產(chǎn)生了新的中斷,那么Distributor就會(huì)把該中斷的狀態(tài)從pending狀態(tài)修改成pending and active。這時(shí)候,CPU interface仍然會(huì)保持nIRQ或者nFIQ信號(hào)的asserted狀態(tài),也就是向processor signal下一個(gè)中斷。
(c)中斷處理完畢的通知。當(dāng)interrupt handler處理完了一個(gè)中斷的時(shí)候,會(huì)向?qū)慍PU interface的寄存器從而通知GIC CPU已經(jīng)處理完該中斷。做這個(gè)動(dòng)作一方面是通知Distributor將中斷狀態(tài)修改為deactive,另外一方面,可以允許其他的pending的interrupt向CPU interface提交。
(d)設(shè)定priority mask。通過priority mask,可以mask掉一些優(yōu)先級(jí)比較低的中斷,這些中斷不會(huì)通知到CPU。
(e)設(shè)定preemption的策略
(f)在多個(gè)中斷事件同時(shí)到來的時(shí)候,選擇一個(gè)優(yōu)先級(jí)最高的通知processor
GIC仲裁單元:為每一個(gè)中斷維護(hù)一個(gè)狀態(tài)機(jī),分別是:inactive、pending、active and pending、active。
下面是來自IHI0048B GIC-V2規(guī)格書3.2.4 Interrupt handling state machine截圖:

GIC檢測(cè)中斷流程如下:
(1) 當(dāng)GIC檢測(cè)到一個(gè)中斷發(fā)生時(shí),會(huì)將該中斷標(biāo)記為pending狀態(tài)(A1)。
(2) 對(duì)處于pending狀態(tài)的中斷,仲裁單元回確定目標(biāo)CPU,將中斷請(qǐng)求發(fā)送到這個(gè)CPU上。
(3) 對(duì)于每個(gè)CPU,仲裁單元會(huì)從眾多pending狀態(tài)的中斷中選擇一個(gè)優(yōu)先級(jí)最高的中斷,發(fā)送到目標(biāo)CPU的CPU Interface模塊上。
(4) CPU Interface會(huì)決定這個(gè)中斷是否可以發(fā)送給CPU。如果該終端優(yōu)先級(jí)滿足要求,GIC會(huì)發(fā)生一個(gè)中斷信號(hào)給該CPU。
(5) 當(dāng)一個(gè)CPU進(jìn)入中斷異常后,會(huì)去讀取GICC_IAR 寄存器來響應(yīng)該中斷(一般是Linux內(nèi)核的中斷處理程序來讀寄存器)。寄存器會(huì)返回硬件中斷號(hào)(hardware interrupt ID),對(duì)于SGI中斷來說是返回源CPU的ID。
當(dāng)GIC感知到軟件讀取了該寄存器后,又分為如下情況:
如果該中斷源是pending狀態(tài),那么轉(zhuǎn)改將變成active。**(C) **
如果該中斷又重新產(chǎn)生,那么pending狀態(tài)變成active and pending。(D)
如果該中斷是active狀態(tài),現(xiàn)在變成active and pending。(A2)(6) 當(dāng)處理器完成中斷服務(wù),必須發(fā)送一個(gè)完成信號(hào)EOI(End Of Interrupt)給GIC控制器。軟件寫GICC_EOIR寄存器,狀態(tài)變成inactive。(E1)
補(bǔ)充:
(7) 對(duì)于level triggered類型中斷來說,當(dāng)觸發(fā)電平消失,狀態(tài)從active and pending變成active。(B2)
常用路徑是A1->D->B2->E1。
1.2.1 GIC中斷搶占
GIC中斷控制器支持中斷優(yōu)先級(jí)搶占,一個(gè)高優(yōu)先級(jí)中斷可以搶占一個(gè)低優(yōu)先級(jí)且處于active狀態(tài)的中斷,即GIC仲裁單元會(huì)記錄和比較當(dāng)前優(yōu)先級(jí)最高的pending狀態(tài),然后去搶占當(dāng)前中斷,并且發(fā)送這個(gè)最高優(yōu)先級(jí)的中斷請(qǐng)求給CPU,CPU應(yīng)答了高優(yōu)先級(jí)中斷,暫停低優(yōu)先級(jí)中斷服務(wù),進(jìn)而去處理高優(yōu)先級(jí)中斷。
GIC會(huì)將pending狀態(tài)優(yōu)先級(jí)最高的中斷請(qǐng)求發(fā)送給CPU。
1.2.2 Linux對(duì)中斷搶占處理
從GIC角度看,GIC會(huì)發(fā)送高優(yōu)先級(jí)中斷請(qǐng)求給CPU。
但是目前CPU處于關(guān)中斷狀態(tài),需要等低優(yōu)先級(jí)中斷處理完畢,直到發(fā)送EOI給GIC。
然后CPU才會(huì)響應(yīng)pending狀態(tài)中優(yōu)先級(jí)最高的中斷進(jìn)行處理。
所以Linux下:
高優(yōu)先級(jí)中斷無法搶占正在執(zhí)行的低優(yōu)先級(jí)中斷。
2.同處于pending狀態(tài)的中斷,優(yōu)先響應(yīng)高優(yōu)先級(jí)中斷進(jìn)行處理。
1.3 GIC中斷時(shí)序

首先給出前提條件:
N和M用來標(biāo)識(shí)兩個(gè)外設(shè)中斷,N的優(yōu)先級(jí)大于M
兩個(gè)中斷都是SPI類型,level trigger,active-high
兩個(gè)中斷被配置為去同一個(gè)CPU
都被配置成 group 0,通過FIQ觸發(fā)中斷
(1) T1時(shí)刻:GIC的總裁單元檢測(cè)到中斷M的電平變化。
(2) T2時(shí)刻:仲裁單元設(shè)置中斷M的狀態(tài)為pending。
(3) T17時(shí)刻:CPU Interface模塊會(huì)拉低nFIQCPU[n]信號(hào)。在中斷M的狀態(tài)變成pending后,大概需要15個(gè)時(shí)鐘周期后會(huì)拉低nFIQCPU[n]信號(hào)來向CPU報(bào)告中斷請(qǐng)求(assertion)。仲裁單元需要這些時(shí)間來計(jì)算哪個(gè)是pending狀態(tài)下優(yōu)先級(jí)最高的中斷。
(4) T42時(shí)刻:仲裁單元檢測(cè)到另外一個(gè)優(yōu)先級(jí)更高的中斷N。
(5) T43時(shí)刻:仲裁單元用中斷N替換中斷M為當(dāng)前pending狀態(tài)下優(yōu)先級(jí)最高的中斷,并設(shè)置中斷N為pending狀態(tài)。
(6) T58時(shí)刻:經(jīng)過tph個(gè)時(shí)鐘后,CPU Interface拉低你FIOCPU[n]信號(hào)來通知CPU。因?yàn)榇诵盘?hào)在T17時(shí)刻已經(jīng)被拉低,CPU Interface模塊會(huì)更新GICC_IAR寄存器的Interrupt ID域,該域的值變成中斷N的硬件中斷號(hào)。
(7) T61~T131時(shí)刻:Linux對(duì)中斷N的服務(wù)程序---------------------中斷服務(wù)程序處理段,從GICC_IAR開始到GICC_EOIR結(jié)束。
T61時(shí)刻:CPU(Linux中斷服務(wù)例程)讀取GICC_IAR寄存器,即軟件響應(yīng)了中斷N。這時(shí)仲裁單元把中斷N的狀態(tài)從pending變成active and pending。
T64時(shí)刻:在中斷N被Linux相應(yīng)3個(gè)時(shí)鐘內(nèi),CPU Interface模塊完成對(duì)nFIQCPU[n]信號(hào)的deasserts,即拉高nFIQCPU[n]信號(hào)。
T126時(shí)刻:外設(shè)也deassert了該中斷N。
T128時(shí)刻:仲裁單元移出了中斷N的pending狀態(tài)。
T131時(shí)刻:Linux服務(wù)程序把中斷N的硬件ID號(hào)寫入GICC_EOIR寄存器來完成中斷N的全部處理過程。
(8) T146時(shí)刻:在向GICC_EOIR寄存器寫入中斷N中斷號(hào)后的tph個(gè)時(shí)鐘后,仲裁單元會(huì)選擇下一個(gè)最高優(yōu)先級(jí)中斷,即中斷M,發(fā)送中斷請(qǐng)求給CPU Interface模塊。CPU Interface會(huì)拉低nFIQCPU[n]信號(hào)來向CPU報(bào)告外設(shè)M的中斷請(qǐng)求。
(9) T211時(shí)刻:Linux中斷服務(wù)程序讀取GICC_IAR寄存器來響應(yīng)中斷,仲裁單元設(shè)置中斷M的狀態(tài)為active and pending。
(10) T214時(shí)刻:在CPU響應(yīng)中斷后的3個(gè)時(shí)鐘內(nèi),CPU Interface模塊拉高nFIOCPU[n]信號(hào)來完成deassert動(dòng)作。
1.4 GIC中 寄存器描述
1.4.1 Distributor

一種實(shí)現(xiàn)是:



1.4.2 CPU Interface


