11. 基於ARM Cortex-A9中斷詳解
一、中斷概念
作業系統中,中斷是很重要的組成部分。出現某些意外情況需主機干預時,機器能自動停止正在執行的程式並轉入處理新情況的程式,處理完畢後又返回原被暫停的程式繼續執行。
有了中斷系統才可以不用一直輪詢(polling)是否有事件發生,系統效率才得以提高。
一般在系統中,中斷控制分為三個部分:模組、中斷控制器和處理器。
其中模組通常由暫存器控制是否使能中斷和中斷觸發條件等;中斷控制器可以管理中斷的優先順序等,而處理器則由暫存器設定用來響應中斷。
二、GIC
作為 ARM 系統中通用中斷控制器的是 GIC(Generic Interrupt Controller),目前有四個版本,V1~V4(V2最多支援8個ARM core,V3/V4支援更多的ARM core,主要用於ARM64系統結構)。
【注意】對於一些老的ARM處理器,比如ARM11,Cortex-A8,中斷控制器一般是VIC(向量中斷控制器)。
1. GIC-400
下面以GIC-400為例,它更適合嵌入式系統,符合v2版本的GIC architecture specification。GIC-400通過AMBA(Advanced Microcontroller Bus Architecture)片上匯流排連線到一個或者多個ARM處理器上。
從上圖可以看出, GIC 是聯絡外設中斷和 CPU 的橋樑,也是各 CPU 之間中斷互聯的通道(也帶有管理功能),它負責檢測、管理、分發中斷,可以做到:
- 使能或禁止中斷;
- 把中斷分組到Group0還是Group1(Group0作為安全模式使用連線FIQ ,Group1 作為非安全模式使用,連線IRQ );
- 多核系統中將中斷分配到不同處理器上;
- 設定電平觸發還是邊沿觸發方式(不等於外設的觸發方式);
- 虛擬化擴充套件。
ARM CPU 對外的連線只有2 箇中斷: IRQ和FIQ ,相對應的處理模式分別是一般中斷(IRQ )處理模式和快速中斷(FIQ )處理模式。所以GIC 最後要把中斷彙集成2 條線,與CPU 對接。
分發器:負責各個子中斷使能,設定觸發方式,優先順序排序,分發到哪個 CPU 上;
介面:負責總的中斷的使能,狀態的維護。
2. 分發器功能
分發器的主要的作用是檢測各個中斷源的狀態,控制各個中斷源的行為,分發各個中斷源產生的中斷事件到指定的一個或者多個CPU介面上。雖然分發器可以管理多箇中斷源,但是它總是把優先順序最高的那個中斷請求送往CPU介面。分發器對中斷的控制包括:
- (a)中斷使能或禁能控制。分發器對中斷的控制分成兩個級別,一個是全域性中斷的控制(GIC_DIST_CTRL),一旦禁能了全域性的中斷,那麼任何的中斷源產生的中斷事件都不會被傳遞到CPU介面;另外一個級別是對針對各個中斷源進行控制(GIC_DIST_ENABLE_CLEAR),禁能某一箇中斷源會導致該中斷事件不會分發到CPU介面,但不影響其他中斷源產生中斷事件的分發。
- (b)控制將當前優先順序最高的中斷事件分發到一個或者一組CPU介面。
- (c)優先順序控制。
- (d)中斷屬性設定,例如是電平觸發還是邊沿觸發。
- (e)中斷的設定。
分發器可以管理若干個中斷源,這些中斷源用ID來標識,我們稱之interrupt ID。
3. CPU介面功能
CPU介面主要用於和CPU進行介面。
主要功能包括:
- (a)使能或者禁能CPU介面向連線的CPU提交中斷事件。對於ARM,CPU介面和CPU之間的中斷訊號線是nIRQCPU和nFIQCPU。如果禁能了中斷,那麼即便是分發器分發了一箇中斷事件到CPU介面,但是也不會提交指定的nIRQ或者nFIQ通知CPU。
- (b)ackowledging中斷。CPU會向CPU介面應答中斷,中斷一旦被應答,分發器就會把該中斷的狀態從pending狀態修改成active,如果沒有後續pending的中斷,那麼CPU 介面就會deassert nIRQCPU和nFIQCPU訊號線。如果在這個過程中又產生了新的中斷,那麼分發器就會把該中斷的狀態從pending狀態修改成pending and active。此時,CPU介面仍然會保持nIRQ或者nFIQ訊號的asserted狀態,也就是向CPU通知下一個中斷。
- (c)中斷處理完畢的通知。當中斷處理器處理完了一箇中斷的時候,會向寫CPU 介面的暫存器從而通知GIC已經處理完該中斷。做這個動作一方面是通知分發器將中斷狀態修改為deactive,另外一方面,可以允許其他的pending的中斷向CPU介面提交。
- (d)設定優先順序掩碼。通過優先順序掩碼可以mask掉一些優先順序比較低的中斷,這些中斷不會通知到CPU。
- (e)設定中斷搶佔的策略。
- (f)在多箇中斷事件同時到來的時候,選擇一個優先順序最高的通知CPU。
以上圖為例,該圖是按鍵產生的中斷訊號要到達cpu所要經過的路徑。
- 外設中斷源有很多,通常晶片廠商會設計若干個第一級中斷控制器,進行第一次處理,key連線的是GPX1中斷控制器,暫存器EXT_INT41_MASK用於使能該中斷;
- GIC主要包括分排氣和cpu interface;
- ICDISER用於使能分派器,ICDIPTR用於將中斷訊號分發給對應的cpu interface;
- ICCICR用於使能CPU interface;
- CPU上有兩個引腳irq、fiq,gic最終會連線到CPU的irq,所有暫存器配置完畢後,按鍵一旦按下,那麼就會給CPU的irq傳送一箇中斷訊號,cpu緊接著就會執行“4大步3小步”,進入中斷異常處理流程。
三、中斷分類
1. 中斷源
硬體中斷(Hardware Interrupt)
- 可遮蔽中斷(maskable interrupt)。硬體中斷的一類,可通過在中斷遮蔽暫存器中設定位掩碼來關閉。
- 非可遮蔽中斷(non-maskable interrupt,NMI)。硬體中斷的一類,無法通過在中斷遮蔽暫存器中設定位掩碼來關閉。典型例子是時鐘中斷(一個硬體時鐘以恆定頻率—如50Hz—發出的中斷)。
- 處理器間中斷(interprocessor interrupt)。一種特殊的硬體中斷。由處理器發出,被其它處理器接收。僅見於多處理器系統,以便於處理器間通訊或同步。
- 偽中斷(spurious interrupt)。一類不希望被產生的硬體中斷。發生的原因有很多種,如中斷線路上電氣訊號異常,或是中斷請求裝置本身有問題。
軟體中斷(Software Interrupt)
軟體中斷SWI,是一條CPU指令,用以自陷一箇中斷。由於軟中斷指令通常要執行一個切換CPU至核心態的子例程,它常被用作實現系統呼叫(System call)。
外部中斷
- I/O裝置:如顯示器、鍵盤、印表機、A / D轉換器等。
- 資料通道:軟盤、硬碟、光碟等。
資料通道中斷也稱直接儲存器存取(DMA)操作中斷,如磁碟、磁帶機或CRT等直接與儲存器交換資料所要求的中斷。 - 實時時鐘:如外部的定時電路等。在控制中遇到定時檢測和控制,為此常採用一個外部時鐘電路(可程式設計)控制其時間間隔。需要定時時,CPU發出命令使時鐘電路開始工作,一旦到達規定時間,時鐘電路發出中斷請求,由CPU轉去完成檢測和控制工作。
- 使用者故障源:如掉電、奇偶校驗錯誤、外部裝置故障等。
產生於CPU內部的中斷源
- 由CPU得執行結果產生:如除數為0、結果溢位、斷點中斷、單步中斷、儲存器讀出出錯等。
- 執行中斷指令swi
- 非法操作或指令引起異常處理。
2. 中斷型別
GIC 中斷型別有3種:SGI(Software-generated interrupt)、PPI(Private peripheral interrupt )、SPI(Shared peripheral interrupt)。
-
SGI: SGI為軟體可以觸發的中斷,統一編號為0~15(ID0-ID7是不安全中斷,ID8-ID15是安全中斷),用於各個core之間的通訊。該類中斷通過相關聯的中斷號和產生該中斷的處理器的CPUID來標識。
通常為邊沿觸發。 -
PPI: PPI為每個 core 的私有外設中斷,統一編號為 16-31 。例如每個 CPU 的 local timer 即 Arch Timer 產生的中斷就是通過 PPI 傳送給 CPU 的(安全為29,非安全為30)。
通常為邊沿觸發和電平觸發。
- SPI: SPI 是系統的外設產生的中斷,為各個 core 公用的中斷,統一編號為 32~1019 ,如 global timer 、uart 、gpio 產生的中斷。
通常為邊沿觸發和電平觸發。
Note:電平觸發是在高或低電平保持的時間內觸發, 而邊沿觸發是由高到低或由低到高這一瞬間觸發;在GIC中PPI和SGI型別的中斷可以有相同的中斷ID。
3. 中斷分派模式
-
1-N mode (SPIs using the GIC 1-N model)
表示中斷可以發給所有的CPU,但只能由一個CPU來處理中斷;換句話說,這種型別的中斷有N個目標CPU,但只能由其中一個來處理;當某一個處理器應答了該中斷,便會清除在所有目標處理器上該中斷的掛起狀態。 -
N-N mode (PPIs and SGIs using the GIC N-N model)
表示中斷可以發給所有CPU,每個CPU可以同時處理該中斷。當該中斷被某一個處理器應答了,這不會影響該中斷在其他CPU介面上的狀態。
舉兩個例子說明:
1)UART 接收到一包資料,產生了一箇中斷給GIC,GIC可以將該中斷分配給CPU0-7中任何一個處理;假設該中斷分配給CPU0處理了,那麼在中斷處理函式裡面會把接收到的資料從UART FIFO讀出。可以想象一下,如果CPU0在讀資料時,另外一個CPU也在處理該中斷,恰巧也在讀資料,那麼CPU0讀到的資料是不全的。這就是1-N model中斷,或者說SPI中斷。
2)比如CPU0給CPU1-7傳送中斷,想告知對方自己正在處理某個程序A。這種場景下,CPU1-7都接收到中斷,都進入中斷處理函式,CPU1-7獲取到CPU0的資訊後,在程序排程時,就可以繞開程序A,而自己排程其他程序。
注:這個例子只是說明N-N model,實際上程序排程不都全是這樣的。
4. 通用中斷處理
當GIC接收到一箇中斷請求,將其狀態設定為Pending。重新產生一個掛起狀態的中斷不影響該中斷狀態。中斷處理順序:
① GIC決定該中斷是否使能,若沒有被使能對GIC沒有影響;
② 對於每個Pending中斷,GIC決定目標處理器;
③ 對於每個處理器 ,Distributor根據它擁有的每個中斷優先順序資訊決定最高優先順序的掛起中斷,將該中斷傳遞給目標CPU Interface;
④ GIC Distributor將一箇中斷傳遞給CPU Interface後,該CPU Interface決定該中斷是否有足夠的優先順序將中斷請求發給CPU;
⑤ 當CPU開始處理該異常中斷,它讀取GICC_IAR應答中斷。讀取的GICC_IAR獲取到中斷ID,對於SGI,還有源處理器ID。中斷ID被用來查詢正確的中斷處理程式。
GIC識別讀過程後,將改變該中斷的狀態:
a) 當中斷狀態變為active時,如果該中斷掛起狀態持續存在或者中斷再次產生,中斷狀態將從Pending轉化為pending & active
b) 否則,中斷狀態將從pending狀態變為active
⑥ 當中斷完成中斷處理後,它需要通知GIC處理已經完成。這個過程稱為 priority drop and interrupt deactivation:
a) 總是需要向EOIR暫存器寫入一個有效的值(end of interrupt register)
b) 也需要接著向GICC_DIR寫入值(deactivate interrupt register)
5. 中斷優先順序
軟體可以通過給每一箇中斷源分配優先順序值來配置中斷優先順序。優先順序的值是個8位的無符號二進位制數,GIC支援最小16和最大256的優先順序級別。如果GIC實現的優先順序少於256,那麼優先順序欄位的低階位為RAZ/WI。這就意味著實現的優先順序欄位個數範圍是4~8,如下圖所示:
Note:
1)、如何確定優先順序欄位所支援的優先順序位?
通過軟體往可寫GICD_IPRIORITYn優先順序欄位寫入0XFF,然後回讀出該欄位的值便可以確定優先順序欄位所支援的優先順序位(因為有些位沒實現是RAZ/WI)
2)、ARM 推薦在檢查中斷優先順序範圍之前先:
• 對於外設中斷,軟體先禁用該中斷
• 對於SGI,軟體先檢查該中斷確定為inactive
6. 中斷搶佔
在一個active中斷處理完之前,CPU interface支援傳送更高優先順序的掛起中斷到目標處理器。這種情況必要條件如下:
- 該中斷的優先順序高於當前CPU interface 被遮蔽的優先順序
- 該中斷的組優先順序高於正在當前CPU interface處理的中斷優先順序
7. 中斷遮蔽
CPU interface的GICC_PMR暫存器定義了目標處理器的優先順序閥值,GIC僅上報優先順序高於閥值的pending中斷給目標處理器。暫存器初始值為0,遮蔽所有的中斷。
四、FS4412中斷外設-key
下面我們來分析FS4412開發板的第一個中斷裝置按鍵。
1. 電路圖
由該電路圖可得:
-
按鍵k2 連線在GPX1_1引腳
-
控制邏輯
k2 按下 ---- K2閉合 ---- GPX1_1 低電壓
k2 常態 ---- K2開啟 ---- GPX1_1 高電壓
以下是key2與soc的連線,
可以看到key2複用了GPIX1_1這個引腳,同時該引腳還可以作為中斷【XEINT9】使用。
順便看下GPXCON暫存器的配置
由上圖所示,
- GPX1CON地址為0x1100C20;
- key2如果要做為輸入裝置,只需要將GPX1CON[7:4]設定為0x0;
- key2如果要做為中斷訊號,只需要將GPX1CON[7:4]設定為0xf。
2. key中斷處理
中斷配置
key與soc的關係圖如下圖所示:
由上圖所示:
- 按鍵是直接連到GPIO控制器的
- EXT_INT_CON用來設定按鍵中斷的觸發方式,下降沿觸發
- GPX1CON暫存器用於設定該GPIO位中斷訊號輸入
- EXT_INT_MASK用於使能該中斷
- ICDISER用於使能相應中斷到分配器
- ICDDCR分配器開關
- ICDIPTR選擇CPU介面
- ICCPMR設定中斷遮蔽優先順序
- ICCICR開啟CPU開關,把CPU介面內的中斷能夠送到相應的CPU
清中斷
CPU處理完中斷,需要清除中斷,對於按鍵來說,有3個暫存器需要操作:
由上圖所示:
- EXT_INT41_PEND清相應的中斷源
- ICDICPR中斷結束後,清相應中斷標誌位,此標誌位由硬體置位
- ICCEOIR中斷執行結束,清cpu內相應的中斷號,由硬體填充
3. 暫存器彙總
前面分析了按鍵連線的是GPX1_1,現在我們來看下對應的暫存器應該如何配置
【1】、GPIO控制器
- GPX1PUD
將GPX1_1引腳的上拉和下拉禁止
GPX1PUD[3:2]= 0b00;
- GPX1CON
將GPX1_1引腳功能設定為中斷功能
GPX1CON[7:4] = 0xf
- EXT_INT41CON
配置成成下降沿觸發:
EXT_INT41CON[6:4] = 0x2
- EXT_INT41_MASK
中斷使能暫存器
EXT_INT41_MASK[1] = 0b0
- EXT_INT41_PEND 中斷狀態暫存器
當GPX1_1引腳接收到中斷訊號,中斷髮生,中斷狀態暫存器EXT_INT41_PEND 相應位會自動置1
注意:中斷處理完成的時候,需要清除相應狀態位。置1清0.
EXT_INT41_PEND[1] =0b1
【2】GIC
根據外設中斷名稱EINT9來檢視該中斷對應的GIC中維護的HW id。【所有的中斷源在晶片廠商設計的時候都分配了唯一的一個ID,GIC通過該ID來驅動中斷源】
檢視晶片手冊(datasheet -- 9.2表)
通過【9.2中斷源表】找到和外設中斷標示對應的中斷控制器中斷標識(GPIO有32個可被喚醒暫存器)其對應EINT[9],中斷ID為57,這是非常重要的,在後面的暫存器設定中起很大作用;
1) ICDISER使能相應中斷到分配器
ICDISER用於使能相應中斷到分配器,一個bit控制一箇中斷源,一個ICDISER可以控制32箇中斷源,這裡INT[9] 對應的中斷ID為57,所以在ICDSER1中進行設定,57/32 =1餘25,所以這裡在ICDISER1第25位置一。
ICDISER.ICDISER1 |= (0x1 << 25); //57/32 =1...25 取整數(那個暫存器) 和餘數(哪位)
- ICDIPTR選擇CPU介面
ICDIPTR暫存器每8個bit 控制一箇中斷源,其中CPU0可以處理160箇中斷源,所以需要40個暫存器。要選擇cpu0第一個bit必須是1。
設定SPI[25]/ID[57]由cpu0處理,57/4=16餘1 所以選擇暫存器ICDIPTR14的第2個位元組[15:8]。
//SPI 25 interrupts are sent to processor 0
//57/4 = 14..1 14號暫存器的[15:8]
ICDIPTR.ICDIPTR14 |= 0x01<<8;
- ICDDCR使能分配器
還暫存器用於使能分配器。
ICDDCR =1;
- ICCPMR
優先順序遮蔽暫存器,設定cpu0能處理所有的中斷。
比如中斷遮蔽優先順序為255,該值表示優先順序最低,所有的中斷都能響應。
CPU0.ICCPMR = 0xFF;//設定cpu0 中斷遮蔽優先順序為255 最低,所有中斷都能響應)
- ICCICR 全域性使能cpu0中斷處理
EXYNOS 4412一共有4個cpu,用4個暫存器分別來控制4個cpu,每個暫存器的bit[0]用於全域性控制對應的cpu。我們選擇cpu0處理中斷,將bit[0]置1即可。
CPU0.ICCICR |= 0x1;
使能中斷到CPU。
- ICCIAR
當中斷髮生之後,中斷的HW id值會由硬體寫入到暫存器ICCIAR[9:0]中;對於SGIs來說,多處理器環境下,CPU的interface值寫入到[12:10]中。
讀取HW id:
int irq_num;
irq_num = CPU0.ICCIAR&0x3ff; //獲取中斷號
五、程式碼實現
要處理中斷異常,必須安裝異常向量表,異常的處理流程可以參考前面的文章《6. 從0開始學ARM-異常、異常向量表、swi》
1. 異常向量表基址
異常向量表地址是可以修改的,比如uboot在啟動的時候,會從flash中搬運程式碼到RAM中,而flash的異常向量表地址和ram的地址肯定不一樣,所以搬運完程式碼後,就必須要修改對應的異常向量表地址。
修改異常向量表的地址的需要藉助協處理器指令mcr:
ldr r0,=0x40008000
mcr p15,0,r0,c12,c0,0 @ Vector Base Address Register
上述命令是將地址0x40008000設定為異常向量表的地址,關於mcr指令,我們沒有必要深究,知道即可。
RAM中異常向量表地址我們選用的是0x40008000,以下是exynos4412 地址空間分佈。
2. 異常向量表安裝
.text
.global _start
_start:
b reset
ldr pc,_undefined_instruction
ldr pc,_software_interrupt
ldr pc,_prefetch_abort
ldr pc,_data_abort
ldr pc,_not_used
ldr pc,=irq_handler
ldr pc,_fiq
reset:
ldr r0,=0x40008000
mcr p15,0,r0,c12,c0,0 @ Vector Base Address Register
init_stack:
//初始化棧
……
b main //跳轉至c的main函式
irq_handler: //中斷入口函式
sub lr,lr,#4
stmfd sp!,{r0-r12,lr}
.weak do_irq
bl do_irq
ldmfd sp!,{r0-r12,pc}^
stacktop: .word stack+4*512//棧頂
.data
stack: .space 4*512 //棧空間
中斷入口函式do_irq()
void do_irq(void)
{
static int a = 1;
int irq_num;
irq_num = CPU0.ICCIAR&0x3ff; //獲取中斷號
switch(irq_num)
{
case 57:
printf("in the irq_handler\n");
//清GPIO中斷標誌位
EXT_INT41_PEND = EXT_INT41_PEND |((0x1 << 1));
//清GIC中斷標誌位
ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (0x1 << 25);
break;
}
//清cpu中斷標誌
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;位
}
實現按鍵中斷的初始化函式key_init():
void key_init(void)
{
GPX1.CON =GPX1.CON & (~(0xf << 4)) |(0xf << 4); //配置引腳功能為外部中斷
GPX1.PUD = GPX1.PUD & (~(0x3 << 2)); //關閉上下拉電阻
EXT_INT41_CON = EXT_INT41_CON &(~(0xf << 4))|(0x2 << 4); //外部中斷觸發方式
EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1 << 1)); //使能中斷
ICDDCR = 1; //使能分配器
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (0x1 << 25); //使能相應中斷到分配器
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xff << 8))|(0x1 << 8); //選擇CPU介面
CPU0.ICCPMR = 255; //中斷遮蔽優先順序
CPU0.ICCICR = 1; //使能中斷到CPU
return ;
}
六、輪詢方式
除了中斷方式之外我們還可以通過輪詢方式讀取按鍵的資訊,原理如下:
迴圈檢測GPX1_1引腳輸入的電平,為低電壓時,按鍵按下,為高電平時,按鍵抬起。
- 配置GPX1_1引腳功能為輸入,設定內部上拉下拉禁止。
GPX1.CON = GPX1.CON &(~(0xf<<4)) ;
GPX1.PUD = GPX1.PUD & ~(0x3 << 2);
-
按鍵消抖:
按鍵按下後由於機械特性,會在極短的時間內出現電平忽0忽1,所以我們檢測到按鍵按下後,需要給一個延時,然後再判斷按鍵是不是仍然按下。 -
程式碼實現
int main (void)
{
led_init();
pwm_init();
GPX1.CON = GPX1.CON &(~(0xf<<4))|0x0<<4;
while(1)
{
if(!(GPX1.DAT & (0x1<<1))) // 返回為真,按鍵按下
{
delay_ms(10);
if(!(GPX1.DAT & (0x1<<1))) //二次檢測,去抖
{
GPX2.DAT |= 0x1 << 7; //Turn on LED2
delay_ms(500);
beep_on();
GPX2.DAT &= ~(0x1<<7); //Turn off LED2
delay_ms(500);
while(!(GPX1.DAT & (0x1<<1)));
beep_off();
}
}
}
return 0;
}
更多 ARM Linux乾貨,請關注 一口Linux