Linux之ARM Cortex-A7 中斷系統詳解【轉】
Cortex-A7 中斷系統詳解
1、中斷是什麼?
中斷系統是一個處理器重要的組成部分,中斷系統極大的提高了 CPU 的執行效率
2、STM32中斷系統和 Cortex-M(STM32)中斷系統的異同
STM32 的中斷系統主要有以下幾個關鍵點:
①、 中斷向量表。
②、 NVIC(內嵌向量中斷控制器)。
③、 中斷使能。
④、 中斷服務函式。
2.1、中斷向量表
中斷向量表是一個表,這個表裡面存放的是中斷向量。中斷服務程式的入口地址或存放中斷服務程式的首地址成為中斷向量,因此中斷向量表是一系列中斷服務程式入口地址組成的表。這些中斷服務程式(函式)在中斷向量表中的位置是由半導體廠商定好的,當某個中斷被觸發以後就會自動跳轉到中斷向量表中對應的中斷服務程式(函式)入口地址處。中斷向量表在整個程式的最前面,比如 STM32F103 的中斷向量表如下所示:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
/* 省略掉其它程式碼 */
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
上表就是 STM32F103 的中斷向量表,中斷向量表都是連結到程式碼的最前面,比如一般 ARM 處理器都是從地址 0X00000000 開始執行指令的,那麼中斷向量表就是從 0X00000000 開始存放的。上表中第 1 行的“__initial_sp”就是第一條中斷向量,存放的是棧頂指標,接下來是第 2 行復位中斷復位函式 Reset_Handler 的入口地址,依次類推,直到第 27 行的最後一箇中斷服務函式 DMA2_Channel4_5_IRQHandler 的入口地址,這樣 STM32F103 的中斷向量表就建好了。
我們說 ARM 處理器都是從地址 0X00000000 開始執行的,但是我們學習 STM32 的時候程式碼是下載到 0X8000000 開始的儲存區域中。因此中斷向量表是存放到 0X8000000 地址處的,而不是 0X00000000,這樣不是就出錯了嗎?為了解決這個問題, Cortex-M 架構引入了一
個新的概念——中斷向量表偏移,通過中斷向量表偏移就可以將中斷向量表存放到任意地址處,中斷向量表偏移配置在函式 SystemInit 中完成,通過向 SCB_VTOR 暫存器寫入新的中斷向量表首地址即可,程式碼如下所示:
void SystemInit (void)
{
RCC->CR |= (uint32_t)0x00000001;
/* 省略其它程式碼 */
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
第 8 行和第 10 行就是設定中斷向量表偏移,第 8 行是將中斷向量表設定到 RAM 中,第10 行是將中斷向量表設定到 ROM 中,基本都是將中斷向量表設定到 ROM 中,也就是地址0X8000000 處。第 10 行用到了 FALSH_BASE 和 VECT_TAB_OFFSET,這兩個都是巨集,定義如下所示:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
- 1
- 2
因此第 10 行的程式碼就是: SCB->VTOR=0X080000000,中斷向量表偏移設定完成。
STM32中斷向量表和中斷向量偏移和I.MX6U中斷向量表和中斷向量偏移的關係?
I.MX6U 所使用的 Cortex-A7 核心也有中斷向量表和中斷向量表偏移,而且其含義和 STM32 是一模一樣的!只是用到的暫存器不同而已,概念完全相同。
2.2、NVIC(內嵌向量中斷控制器)
中斷系統得有個管理機構,對於 STM32 這種 Cortex-M 核心的微控制器來說這個管理機構叫做 NVIC,全稱叫做 Nested Vectored Interrupt Controller。關於 NVIC 本教程不作詳細的講解,既然 Cortex-M 核心有個中斷系統的管理機構—NVIC,那麼 I.MX6U 所使用的 Cortex-A7 核心是不是也有個中斷系統管理機構?答案是肯定的,不過 Cortex-A 核心的中斷管理機構不叫做NVIC,而是叫做 GIC,全稱是 general interrupt controller。
2.3、中斷使能
要使用某個外設的中斷,肯定要先使能這個外設的中斷,以 STM32F103 的 PE2 這個 IO 為例,假如我們要使用 PE2 的輸入中斷肯定要使用如下程式碼來使能對應的中斷:
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //搶佔優先順序 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子優先順序 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure);
- 1
- 2
- 3
- 4
- 5
上述程式碼就是使能 PE2 對應的 EXTI2 中斷,同理,如果要使用 I.MX6U 的某個中斷的話也需要使能其對應的中斷。
2.4、中斷服務函式
我們使用中斷的目的就是為了使用中斷服務函式,當中斷髮生以後中斷服務函式就會被呼叫,我們要處理的工作就可以放到中斷服務函式中去完成。同樣以 STM32F103 的 PE2 為例,其中斷服務函式如下所示:
/* 外部中斷 2 服務程式 */
void EXTI2_IRQHandler(void)
{
/* 中斷處理程式碼 */
}
- 1
- 2
- 3
- 4
- 5
當PE2 引腳的中斷觸發以後就會呼叫其對應的中斷處理函式 EXTI2_IRQHandler,我們可以在函式 EXTI2_IRQHandler 中新增中斷處理程式碼。同理, I.MX6U 也有中斷服務函式,當某個外設中斷髮生以後就會呼叫其對應的中斷服務函式。
3、Cortex-A7 中斷系統詳解
3.1、Cortex-A7 中斷系統簡介
跟 STM32 一樣, Cortex-A7 也有中斷向量表,中斷向量表也是在程式碼的最前面。 CortexA7 核心有 8 個異常中斷,這 8 個異常中斷的中斷向量表如下表所示:
向量地址 | 終端型別 | 中斷模式 |
---|---|---|
0X00 | 復位中斷(Rest) | 特權模式(SVC) |
0X04 | 未定義指令中斷(Undefined Instruction) | 未定義指令中止模式(Undef) |
0X08 | 軟中斷(Software Interrupt,SWI) | 特權模式(SVC) |
0X0C | 指令預取中止中斷(Prefetch Abort) | 中止模式 |
0X10 | 資料訪問中止中斷(Data Abort) | 中止模式 |
0X14 | 未使用(Not Used) | 未使用 |
0X18 | IRQ 中斷(IRQ Interrupt) | 外部中斷模式(IRQ) |
0X1C | FIQ 中斷(FIQ Interrupt) | 快速中斷模式(FIQ) |
中斷向量表裡面都是中斷服務函式的入口地址,因此一款晶片有什麼中斷都是可以從中斷向量表看出來的。從上表中可以看出, Cortex-A7 一共有 8 箇中斷,而且還有一箇中斷向量未使用,實際只有 7 箇中斷。和“示例程式碼的 STM32F103 中斷向量表比起來少了很多!難道一個能跑 Linux 的晶片只有這 7 箇中斷?明顯不可能的!那類似 STM32 中的EXTI9_5_IRQHandler、 TIM2_IRQHandler 這樣的中斷向量在哪裡? I2C、 SPI、定時器等等的中斷怎麼處理呢?這個就是 Cortex-A 和 Cotex-M 在中斷向量表這一塊的區別,對於 Cortex-M 核心來說,中斷向量表列舉出了一款晶片所有的中斷向量,包括晶片外設的所有中斷。對於 CotexA 核心來說並沒有這麼做,在上表中有個 IRQ 中斷, Cortex-A 核心 CPU 的所有外部中斷都屬於這個 IQR 中斷,當任意一個外部中斷髮生的時候都會觸發 IRQ 中斷。在 IRQ 中斷服務函式裡面就可以讀取指定的暫存器來判斷髮生的具體是什麼中斷,進而根據具體的中斷做出相應的處理。這些外部中斷和 IQR 中斷的關係如圖 所示:
在上圖中,左側的 Software0_IRQn~PMU_IRQ2_IRQ 這些都是 I.MX6U 的中斷,他們都屬於 IRQ 中斷。當圖左側這些中斷中任意一個發生的時候 IRQ 中斷都會被觸發,所以我們需要在 IRQ 中斷服務函式中判斷究竟是左側的哪個中斷髮生了,然後再做出具體的處理。
在 中斷系統表中一共有 7 箇中斷,簡單介紹一下這 7 箇中斷:
中斷 | 描述 |
---|---|
①、復位中斷(Rest) | CPU 復位以後就會進入復位中斷,我們可以在復位中斷服務函式裡面做一些初始化工作,比如初始化 SP 指標、 DDR 等等。 |
②、未定義指令中斷(Undefined Instruction) | 如果指令不能識別的話就會產生此中斷。 |
③、軟中斷(Software Interrupt,SWI) | 由 SWI 指令引起的中斷, Linux 的系統呼叫會用 SWI指令來引起軟中斷,通過軟中斷來陷入到核心空間。 |
④、指令預取中止中斷(Prefetch Abort) | 預取指令的出錯的時候會產生此中斷。 |
⑤、資料訪問中止中斷(Data Abort) | 訪問資料出錯的時候會產生此中斷。 |
⑥、 IRQ 中斷(IRQ Interrupt) | 外部中斷,前面已經說了,晶片內部的外設中斷都會引起此中斷的發生。 |
⑦、 FIQ 中斷(FIQ Interrupt) | 快速中斷,如果需要快速處理中斷的話就可以使用此中。 |
在上面的 7 箇中斷中,我們常用的就是復位中斷和 IRQ 中斷,所以我們需要編寫這兩個中
斷的中斷服務函式。
首先我們要根據表 的內容來建立中斷向量表,中斷向量表處於程式最開始的地方,比如我們前面例程的 start.S 檔案最前面,中斷向量表如下:
.global _start /* 全域性標號 */
_start:
ldr pc, =Reset_Handler /* 復位中斷 */
ldr pc, =Undefined_Handler /* 未定義指令中斷 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中斷 */
ldr pc, =PrefAbort_Handler /* 預取終止中斷 */
ldr pc, =DataAbort_Handler /* 資料終止中斷 */
ldr pc, =NotUsed_Handler /* 未使用中斷 */
ldr pc, =IRQ_Handler /* IRQ 中斷 */
ldr pc, =FIQ_Handler /* FIQ(快速中斷)未定義中斷 */
/* 復位中斷 */
Reset_Handler:
/* 復位中斷具體處理過程 */
/* 未定義中斷 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/* SVC 中斷 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 預取終止中斷 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
/* 資料終止中斷 */
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/* 未使用的中斷 */
NotUsed_Handler:
ldr r0, =NotUsed_Handler
bx r0
/* IRQ 中斷!重點!!!!! */
IRQ_Handler:
/* 復位中斷具體處理過程 */
FIQ_Handler:
ldr r0, =FIQ_Handler
bx r0
第 4 到 11 行是中斷向量表,當指定的中斷髮生以後就會呼叫對應的中斷復位函式,比如復位中斷髮生以後就會執行第 4 行程式碼,也就是呼叫函式 Reset_Handler,函式 Reset_Handler就是復位中斷的中斷復位函式,其它的中斷同理。
第 14 到 50 行就是對應的中斷服務函式,中斷服務函式都是用匯編編寫的,我們實際需要編寫的只有復位中斷服務函式 Reset_Handler 和 IRQ 中斷服務函式 IRQ_Handler,其它的中斷本教程沒有用到,所以都是死迴圈。在編寫復位中斷復位函式和 IRQ 中斷服務函式之前我們還需要了解一些其它的知識,否則的話就沒法編寫。
3.2、GIC 控制器簡介
3.2.1、 GIC 控制器總覽
I.MX6U(Cortex-A)的中斷控制器叫做 GIC
GIC 是 ARM 公司給 Cortex-A/R 核心提供的一箇中斷控制器,類似 Cortex-M 核心中的NVIC。目前 GIC 有 4 個版本:V1~V4, V1 是最老的版本,已經被廢棄了。 V2~V4 目前正在大量的使用。 GIC V2 是給 ARMv7-A 架構使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是給 ARMv8-A/R 架構使用的,也就是 64 位晶片使用的。 I.MX6U 是 Cortex-A 核心的,因此我們主要講解 GIC V2。 GIC V2 最多支援 8 個核。 ARM 會根據 GIC 版本的不同研發出不同的 IP 核,那些半導體廠商直接購買對應的 IP 核即可,比如 ARM 針對 GIC V2 就開發出了 GIC400 這個中斷控制器 IP 核。當 GIC 接收到外部中斷訊號以後就會報給 ARM 核心,但是ARM 核心只提供了四個訊號給 GIC 來彙報中斷情況: VFIQ、 VIRQ、 FIQ 和 IRQ,他們之間的關係如圖所示:
在上圖中, GIC 接收眾多的外部中斷,然後對其進行處理,最終就只通過四個訊號報給 ARM 核心,這四個訊號的含義如下:
- VFIQ:虛擬快速 FIQ。
- VIRQ:虛擬快速 IRQ。
- FIQ:快速中斷 IRQ。
- IRQ:外部中斷 IRQ
本博文我們只使用 IRQ,所以相當於 GIC 最終向 ARM 核心就上報一個 IRQ訊號。那麼 GIC 是如何完成這個工作的呢? GICV2 的邏輯圖如圖下所示:
圖中左側部分就是中斷源,中間部分就是 GIC 控制器,最右側就是中斷控制器向處理器核心傳送中斷資訊。我們重點要看的肯定是中間的 GIC 部分, GIC 將眾多的中斷源分為分為三類:
-
①、 SPI(Shared Peripheral Interrupt),共享中斷,顧名思義,所有 Core 共享的中斷,這個是最常見的,那些外部中斷都屬於 SPI 中斷(注意!不是 SPI 匯流排那個中斷) 。比如按鍵中斷、串列埠中斷等等,這些中斷所有的 Core 都可以處理,不限定特定 Core。
-
②、 PPI(Private Peripheral Interrupt),私有中斷,我們說了 GIC 是支援多核的,每個核肯定有自己獨有的中斷。這些獨有的中斷肯定是要指定的核心處理,因此這些中斷就叫做私有中斷。
-
③、 SGI(Software-generated Interrupt),軟體中斷,由軟體觸發引起的中斷,通過向暫存器GICD_SGIR 寫入資料來觸發,系統會使用 SGI 中斷來完成多核之間的通訊。
3.2.2、中斷 ID
中斷源有很多,為了區分這些不同的中斷源肯定要給他們分配一個唯一 ID,這些 ID 就是中斷 ID。每一個 CPU 最多支援 1020 箇中斷 ID,中斷 ID 號為 ID0~ID1019。這 1020 個 ID 包含了 PPI、 SPI 和 SGI,那麼這三類中斷是如何分配這 1020 箇中斷 ID 的呢?這 1020 個 ID 分配如下:
- ID0~ID15:這 16 個 ID 分配給 SGI。
- ID16~ID31:這 16 個 ID 分配給 PPI。
- ID32~ID1019:這 988 個 ID 分配給 SP。
I.MX6U 的總共使用了 128 箇中斷 ID,加上前面屬於 PPI 和 SGI 的 32 個 ID, I.MX6U 的中斷源共有 128+32=160個,這 128 箇中斷 ID 對應的中斷在《I.MX6ULL 參考手冊》的“3.2 Cortex A7 interrupts”小節,中斷源如表 所示:
IRQ | ID | 中斷源 | 描述 |
---|---|---|---|
0 | 32 | boot | 用於在啟動異常的時候通知核心。 |
1 | 33 | ca7_platform | DAP 中斷,除錯埠訪問請求中斷。 |
2 | 34 | sdma | SDMA 中斷。 |
3 | 35 | tsc | TSC(觸控)中斷。 |
4 | snvs_lp_wrapper 和snvs_hp_wrapper | SNVS 中斷。 | |
… | … | …… | …… |
124 | 156 | 無 | 保留 |
125 | 157 | 無 | 保留 |
126 | 158 | 無 | 保留 |
127 | 159 | pmu | PMU 中斷 |
這裡只列舉了一小部分並沒有給出 I.MX6U 完整的中斷源
NXP 官方 SDK中的檔案 MCIMX6Y2C.h,在此檔案中定義了一個列舉型別 IRQn_Type,此列舉型別就枚舉出了 I.MX6U 的所有中斷,程式碼如下所示:
typedef enum IRQn {
/* Auxiliary constants */
NotAvail_IRQn = -128, /**< Not available device specific interrupt */
/* Core interrupts */
Software0_IRQn = 0, /**< Cortex-A7 Software Generated Interrupt 0 */
Software1_IRQn = 1, /**< Cortex-A7 Software Generated Interrupt 1 */
Software2_IRQn = 2, /**< Cortex-A7 Software Generated Interrupt 2 */
Software3_IRQn = 3, /**< Cortex-A7 Software Generated Interrupt 3 */
Software4_IRQn = 4, /**< Cortex-A7 Software Generated Interrupt 4 */
Software5_IRQn = 5, /**< Cortex-A7 Software Generated Interrupt 5 */
Software6_IRQn = 6, /**< Cortex-A7 Software Generated Interrupt 6 */
Software7_IRQn = 7, /**< Cortex-A7 Software Generated Interrupt 7 */
Software8_IRQn = 8, /**< Cortex-A7 Software Generated Interrupt 8 */
Software9_IRQn = 9, /**< Cortex-A7 Software Generated Interrupt 9 */
Software10_IRQn = 10, /**< Cortex-A7 Software Generated Interrupt 10 */
Software11_IRQn = 11, /**< Cortex-A7 Software Generated Interrupt 11 */
Software12_IRQn = 12, /**< Cortex-A7 Software Generated Interrupt 12 */
Software13_IRQn = 13, /**< Cortex-A7 Software Generated Interrupt 13 */
Software14_IRQn = 14, /**< Cortex-A7 Software Generated Interrupt 14 */
Software15_IRQn = 15, /**< Cortex-A7 Software Generated Interrupt 15 */
VirtualMaintenance_IRQn = 25, /**< Cortex-A7 Virtual Maintenance Interrupt */
HypervisorTimer_IRQn = 26, /**< Cortex-A7 Hypervisor Timer Interrupt */
VirtualTimer_IRQn = 27, /**< Cortex-A7 Virtual Timer Interrupt */
LegacyFastInt_IRQn = 28, /**< Cortex-A7 Legacy nFIQ signal Interrupt */
SecurePhyTimer_IRQn = 29, /**< Cortex-A7 Secure Physical Timer Interrupt */
NonSecurePhyTimer_IRQn = 30, /**< Cortex-A7 Non-secure Physical Timer Interrupt */
LegacyIRQ_IRQn = 31, /**< Cortex-A7 Legacy nIRQ Interrupt */
/* Device specific interrupts */
IOMUXC_IRQn = 32, /**< General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition while boot. */
DAP_IRQn = 33, /**< Debug Access Port interrupt request. */
SDMA_IRQn = 34, /**< SDMA interrupt request from all channels. */
TSC_IRQn = 35, /**< TSC interrupt. */
SNVS_IRQn = 36, /**< Logic OR of SNVS_LP and SNVS_HP interrupts. */
LCDIF_IRQn = 37, /**< LCDIF sync interrupt. */
RNGB_IRQn = 38, /**< RNGB interrupt. */
CSI_IRQn = 39, /**< CMOS Sensor Interface interrupt request. */
PXP_IRQ0_IRQn = 40, /**< PXP interrupt pxp_irq_0. */
SCTR_IRQ0_IRQn = 41, /**< SCTR compare interrupt ipi_int[0]. */
SCTR_IRQ1_IRQn = 42, /**< SCTR compare interrupt ipi_int[1]. */
WDOG3_IRQn = 43, /**< WDOG3 timer reset interrupt request. */
Reserved44_IRQn = 44, /**< Reserved */
APBH_IRQn = 45, /**< DMA Logical OR of APBH DMA channels 0-3 completion and error interrupts. */
WEIM_IRQn = 46, /**< WEIM interrupt request. */
RAWNAND_BCH_IRQn = 47, /**< BCH operation complete interrupt. */
RAWNAND_GPMI_IRQn = 48, /**< GPMI operation timeout error interrupt. */
UART6_IRQn = 49, /**< UART6 interrupt request. */
PXP_IRQ1_IRQn = 50, /**< PXP interrupt pxp_irq_1. */
SNVS_Consolidated_IRQn = 51, /**< SNVS consolidated interrupt. */
SNVS_Security_IRQn = 52, /**< SNVS security interrupt. */
CSU_IRQn = 53, /**< CSU interrupt request 1. Indicates to the processor that one or more alarm inputs were asserted. */
USDHC1_IRQn = 54, /**< USDHC1 (Enhanced SDHC) interrupt request. */
USDHC2_IRQn = 55, /**< USDHC2 (Enhanced SDHC) interrupt request. */
SAI3_RX_IRQn = 56, /**< SAI3 interrupt ipi_int_sai_rx. */
SAI3_TX_IRQn = 57, /**< SAI3 interrupt ipi_int_sai_tx. */
UART1_IRQn = 58, /**< UART1 interrupt request. */
UART2_IRQn = 59, /**< UART2 interrupt request. */
UART3_IRQn = 60, /**< UART3 interrupt request. */
UART4_IRQn = 61, /**< UART4 interrupt request. */
UART5_IRQn = 62, /**< UART5 interrupt request. */
eCSPI1_IRQn = 63, /**< eCSPI1 interrupt request. */
eCSPI2_IRQn = 64, /**< eCSPI2 interrupt request. */
eCSPI3_IRQn = 65, /**< eCSPI3 interrupt request. */
eCSPI4_IRQn = 66, /**< eCSPI4 interrupt request. */
I2C4_IRQn = 67