教程I.MX6U的中斷系統講解是從STM32引入的,這就對我這種沒接觸過STM32的小白不太友好!並且中斷可以說是到目前為止最最重要的知識點。還好,STM32只是大致過了幾個知識點
STM32的中斷系統回顧
參考教程給出的STM32的中斷系統,主要有下面幾個知識點
- 中斷向量表
- 向量中斷控制器NVIC
- 中斷使能
- 中斷服務函數
我們一個個來看看!
中斷向量表
中斷向量表說白了也是一個表,表里放的是中斷向量,中斷服務程序的入口地址或者存放中斷程序服務的首地址稱為中斷向量,所以說中斷向量表也就是一系列中斷服務程序入口地址組成的表。這些中斷服務程序或函數在中斷向量表里的位置是由半導體廠商定好的,當某個中斷倍觸發后會自動跳轉到向量表中對應中斷服務對應的入口地址。中斷向量表在整個程序的最前面。
1 __Vectors DCD __initial_sp ; Top of Stack 2 DCD Reset_Handler ; Reset Handler 3 DCD NMI_Handler ; NMI Handler 4 DCD HardFault_Handler ; Hard Fault Handler 5 DCD MemManage_Handler ; MPU Fault Handler 6 DCD BusFault_Handler ; Bus Fault Handler 7 DCD UsageFault_Handler ; Usage Fault Handler
上面的這一段代碼就節選自STM32F103的中斷向量表,中斷向量表都是鏈接在代碼的最前面,比如ARM的處理器是從0x00000000開始執行代碼,那么這個向量表就是從0x00000000開始存放的,代碼第一行的__initial_sp就是第一條中斷向量,存放的是棧盯指針,下面依次是復位等函數的入口地址,一直到最后這個向量表就完成了。
中斷向量偏移
在第一個通過匯編點亮LED等試驗中,我們是把程序鏈接到0x87800000,那么就是從這個地址開始執行指令的,那么不就出錯了么?Cortex-M架構引入了一個新的概念:中斷向量偏移,通過這個偏移就可以把向量表存儲到任何地址。中斷向量偏移要在函數SystemInit中完成,通過向SCB_VTOR寄存器寫入新的中斷向量表的首地址就行了。
內嵌向量中斷控制器
內嵌向量中斷控制器NVIC(Nested Vectored Interrupt Controller)是對STM32進行中斷管理的機構,這個控制器負責協調各個中斷的管理。具體原理這里不深究了,但是NVIC是針對Cortex-M內核的,我們這次使用的Cortex-A內核的管理機構不叫NVIC,而是GIC(General Interrupt Controller)。具體作用后面會降到。
中斷使能
要想要使用某個外設引發的中斷,必須要使能這個外設的中斷,這個沒什么可講的,直接設置相關寄存器就可以了。
中斷服務函數
我們使用中斷主要目的就是使用中斷服務函數,當中斷發生后當前程序被掛起,中斷服務函數被調用,中斷服務函數執行完畢后回到原流程。
上面就是STM32整個中斷系統的大致說明,Cortex-M內核的中斷處理過程和Cortex-A內核的處理流程大同小異,我們看看Cortex-A的中斷處理是什么樣的。
Cortex-A7中斷系統簡介
中斷向量表
和STM32一樣,Cortex-A也是有個中斷向量表的,這個表也是在代碼的最前面,Cortex-A7內核里只有8個異常中斷,這8個異常中斷等中斷模式如下表(ARM Cortex-A(armV7)編程手冊V4.0,第11.1.1章節)
Normal Vector offset | High vector address | Non-secure | Secure | Hypervisor | Monitor |
0x0 | 0xFFFF0000 | Not used | Reset | Reset | Not used |
0x4 | 0xFFFF0004 | Undefined instruction | Undefined instruction | Undefined instruction from Hyp mode | Not used |
0x8 | 0xFFFF0008 | Supervisor Call | Supervisior Call | Secure Monitor Call | Secure Monitor Call |
0xC | 0xFFFF000C | Prefetch Abort | Prefetch Abort | Prefetch Abort from Hyp mode | Prefetch Abort |
0x10 | 0xFFFF0010 | Data Abort | Data Abort | Data Abort from Hyp mode | Data Abort |
0x14 | 0xFFFF0014 | Not used | Not used | Hyp mode entry | Not used |
0x18 | 0xFFFF0018 | IRQ Interrupt | IRQ Interrupt | IRQ Interrupt | IRQ Interrupt |
0x1C | 0xFFFF001C | FIQ Interrupt | FIQ Interrupt | FIQ Interrupt | FIQ Interrupt |
我在編程手冊上只找到了上面這個中斷模式的表,沒有找到對應的中斷類型,教程上給出了相對應的中斷類型,這里截個圖
可以看出來,這個中斷向量表只有8個中斷,而且還有個未使用,也就是說Cortex-A7的要處理的中斷要比Cortex-M少的多,明顯不可能。區別就是,Cortex-M在中斷向量表里列出了包括所有外設的所有的中斷向量,而對於Cortex-A7來說,把所有外部中斷都給了IRQ中斷。任何一個外部中斷觸發中斷時都會觸發IRQ中斷,在IRQ中斷服務中讀取指定的寄存器來判定是什么中斷,然后做出相應的中斷處理。
異常中斷介紹
從上面那個表可以看出來Cortex-A7一共用了7種異常中斷,下面簡單介紹一下這7種中斷的作用
- Reset,復位中斷,CPU復位后就會進入復位中斷,在中斷服務函數中我們需要做一些初始化服務,例如初始化SP指針,DDR等工作。
- Undef未定義指令中斷,當指令不能識別的時候就會產生此中斷。
- SWI軟中斷,由軟件指令引發的中斷,Linux系統調用會用SWI指令引發中斷。
- PrefetchAbort指令預取中止中斷,預取指令出錯時會觸發此中斷。
- Data Abort 數據訪問中止中斷,當訪問數據出錯時產生此中斷。
- IRQ外部中斷,芯片內部的外設中斷都會引發此中斷。
- FIQ快速中斷,需要快速處理中斷時可以使用此中斷。
代碼構成
由於整個代碼比較長,我們把將其按照這幾塊分割下。下面就是中斷向量表的創建
.global _start _start: @添加中斷向量表 ldr pc,=Reset_Handler //復位中斷函數 ldr pc,=Undefined_Handler //未定義指令中斷函數 ldr pc,=SVC_Handler //SVC ldr pc,=PreAbort_Handler //預取終止 ldr pc,=DataAbort_Handler //數據終止 ldr pc,=NotUsed_Handler //未使用 ldr pc,=IRQ_Handler //IRQ中斷 ldr pc,=FIQ_Handler //FIQ中斷 /*復位中斷函數 */ Reset_Handler: ldr r0,=Reset_Handler bx r0 /*未定義指令中斷服務函數 */ Undefined_Handler: ldr r0,=Undefined_Handler bx r0 /*未定義指令中斷服務函數 */ Undefined_Handler: ldr r0,=Undefined_Handler bx r0 /*SVC中斷服務函數 */ SVC_Handler: ldr r0,=SVC_Handler bx r0 /*預取終值中斷服務函數 */ PreAbort_Handler: ldr r0,=PreAbort_Handler bx r0 /*數據終值中斷服務函數 */ DataAbort_Handler: ldr r0,=DataAbort_Handler bx r0 /*未使用中斷服務函數 */ NotUsed_Handler: ldr r0,=NotUsed_Handler bx r0 /*IRQ中斷服務函數 */ IRQ_Handler: ldr r0,=IRQ_Handler bx r0 /*FIQ中斷服務函數 */ FIQ_Handler: ldr r0,=FIQ_Handler bx r0
可以看出來,中斷向量表是從_start開始的,也就是程序的入口,向量表后面緊跟着各個中斷服務函數。當CPU響應中斷,就會從向量表里根據定義的函數執行相應的服務。上面的每個服務里都是沒有寫具體的程序,只是做了個一個死循環。
GIC控制器
前面已經提到過,STM32是通過NVIC來控制中斷的,而I.MX6U是通過一個叫做GIC的中斷控制器來控制中斷的。有個手冊是專門介紹這個控制器的(ARM Generic Interrupt Controller V2.0)。
GIC是ARM公司給Cortex-A/R內核提供的一個中斷控制器,有4個版本(V1~V4)。各個版本支持的Soc如下表
I.MXU6屬於CortexA7架構,使用的是V2版,最多支持8個核。當GIC接受到外部中斷后就會報給ARM內核,如下圖所示
但是從上圖中的CPU interface到Processor之間,內核只提供了4個通道用來提供信號通訊:VFIQ,VIRQ,FIQ和IRQ
上面圖下標也說明了:VFIQ和VIRQ就是虛擬的FIQ和IRQ。所以我們現在只用處理FIQ和IRQ了。在這個教程里,我們只使用IRQ,相當於GIC最后只通過IRQ上報了中斷的信息。那么,GIC是如何完成這個工作的呢?下面的圖就是GICV2的邏輯示意圖
上圖中,左側部分就是中斷源,中間部分相當於GIC,右邊的就是GIC向內核發送的中斷信息。可以從上圖里發現,GIC將外部中斷分了3類:
SPI(Shared Peripheral Interrupt)共享中斷(一定要注意這個SPI不是那個總線的SPI),就是所有Core都共享的中斷,這個是最常見的,比如按鍵、串口等外部的中斷都屬於SPI中斷,所有的Core都可以處理該中斷。
PPI(Private Peripheral Interrupt)私有中斷,我們上面說過,V2版本的GIC最多支持8個核,那么每個核都會有一些自己獨有的中斷,這些中斷需要指定的核去處理。
SGI(Software-generated Interrupt)軟件中斷,有軟件觸發的中斷,通過向寄存器GICD_SGIR寫入數據來觸發,系統會使用SGI中斷來完成多核之間的通訊。
中斷ID
因為所有的外部中斷源最終都是引發的IRQ,那么為了區分不同的中斷源需要給他們分配一個唯一的ID,這些ID就是中斷ID。每一個CPU支持1020個中斷ID,編號為ID0~ID1019.這1020個ID包含了SGI、PPI和SPI,他們是如下分配的:
- ID0~ID15:共16個,分配給SGI
- ID16~ID31:共16個分配給PPI
- ID32~ID1019:988個分配給SPI
這些ID是半導體廠商事先定義好的,I.MX6ULL(注意這里是6ULL)我們要去參考手冊里查,可以發現6ULL使用了128個中斷ID,所以加上前面的32個ID,一共有160個中斷ID。這128個中斷ID對應的中斷源在參考手冊第三章可以查到
注意IRQ的起始值不是從0開始的,是從32開始的,前面32個是預留給PPI和SPI的。我們前面移植了恩智浦官方的SDK,里面提供了一個枚舉類型IRQn_Type,里面就枚舉了所有的中斷。(6UL和6ULL的中斷描述不太一樣,教程里截取的表格是6ULL的)
GIC的邏輯分區
GIC架構按照功能邏輯分了兩個邏輯塊:Distributor分發器和 CPU Interface,兩個邏輯塊作用是這樣的
Distributor
負責處理各個中斷時間段分發問題,決定中斷事件發送到那個CPU Interface,分發器收集所有的中斷源,控制每個控制的優先級並將優先級高的事件發送給CPU接口。其主要工作如下:
- 全局中斷使能控制
- 控制每個中斷的使能
- 設置每個中斷的優先級
- 設置每個中斷等目標處理器列表
- 設置每個外部中斷等觸發模式:電平觸發或邊沿觸發
- 設置每個中斷屬於組0還是組1
CPU Interface
CPU端口是和CPU核相連接,所以每個CPU核都有個與其對應的CPU Interface。它是分發器和CPU核之間的橋梁,主要工作如下:
- 使能或關閉發送到CPU核對終端請求信號
- 應答中斷
- 通知中斷處理完成
- 設置優先級掩碼,通過掩碼來設置哪些中斷不需要上報給CPU核處理
- 定義搶占策略
- 當多個中斷請求時,選擇優先級高度中斷通知CPU核。
下面的表《Cortex-A7 Technical ReferenceManua.pdf》8.2.1GIC memory-map就是Distributor和CPU接口的偏移地址
CP15協處理器
這一小節講的比較散,因為教程上是直接跳到這里就修改寄存器的值達到關閉I Cache、D Cache和MMU的目的。只有過程,原因不是很清楚。
我們在GIC手冊里看一下相關寄存器的地址
注意表格抬頭,是個偏移地址。這個偏移量基於GICD的偏移地址這個GICD就是上面那個GIC Memory Map的Distributor的地址上偏移來的(0x1000~0x1FFF),但是GICD的基地址也是個基於GIC基地址偏移量,要獲取GIC的基地址,就要用到一個叫做CP15協處理器的東西。
CP15協處理器用於系統存儲管理,但是在中斷中也能用到,CP15一共只用16個32位的寄存器,需要通過下面兩條指令完成操作
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2> MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2> cond:指令執行的條件碼,如果忽略的話就表示無條件執行。 opc1:協處理器要執行的操作碼。 Rt:ARM 源寄存器,要寫入到 CP15 寄存器的數據就保存在此寄存器中。 CRn:CP15 協處理器的目標寄存器。 CRm:協處理器中附加的目標寄存器或者源操作數寄存器 CRm 設置為 C0,否則結果不可預測。 opc2:可選的協處理器特定操作碼,當不需要的時候要設置為 0。
CP15協處理器的操作要在 《 ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第B3.17章節里和《Cortex-A7 Technical ReferenceManua.pdf》第四章里講的有。
CP15里的16個32位寄存器為c0~c15,中斷要用到c0、c1、c12和c15這四個寄存器,其他的寄存器作用可以在上面兩個文檔中查詢。並且這個CP15里的16個寄存器,在opc1和opc2不同的話代表操作的寄存器是不同的,這個比較有意思。
從上面的c0的圖表可以看出來,即便是指令前面的參數都一樣,在opc2不同的時候代表的參數是不一樣的。當opc1=0,CRm=c0,opc2=0時c0就是主ID寄存器MIDR,這個就是c0的基本作用,而c1的基本作用是SCTLR寄存器,用來完成控制作用,比如使能MMU、I Cache、
D Cache等。所有的c0~15一共16個寄存器的操作全在《 ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第B3.17里給了說明(Page1481:Table3-42給了詳細的說明),在《Cortex-A7 Technical ReferenceManua.pdf》第4.2節也對每個寄存器的不同bit的作用進行過介紹,並且每個操作寄存器用到的指令都有說明。
c12寄存器我們需要定義為CBAR寄存器
這個VBAR通用名字就可以看出來是GIC的基地址,通過定義這個寄存器的數據就可以設置中斷向量偏移。
c15的說明在《Cortex-A7 Technical ReferenceManua.pdf》里,我們需要它定義為CBAR寄存器,這個寄存器里保存了GIC的基地址。
到這里,就接到前面講到內容了,如果我們需要獲取當前中斷ID,當前的中斷ID是保存在 GICC_IAR中,而GICC_IAR屬於CPU端口寄存器,地址偏移量為0xC,那么我們就從c15按照CBAR模式讀取到GIC底基地址加上偏移量就是對應的值,再獲取bit[9:0]就是中斷ID
MRC p15,4,r1,c15,c0,0 @獲取GIC基地址,保存至r1 ADD r1,r1,#0x2000 @GIC基地址加上0x2000得到CPU接口寄存器起始地址 LDR r0,[r1,#0xC] @讀取端口起始地址+0xC地址,也就是GICC_IAR的值
所以也就是說,我們要操作中斷,就要需要修改c0,c1,c12和c15的參數。
中斷使能
中斷使能包括兩部分:IRQ或者FIQ總使能,還有針對ID0~ID1019這1020個中斷源的使能。
總中斷使能
想要使用中斷,必須打開IRQ或FIQ中斷使能這個是短短是通過當前程序狀態寄存器CPSR控制的bit[7](IRQ)和bit[6](FIQ)控制的(0時為使能),匯編提供了更簡單的指令來對其進行操作
指令 | 功能 |
cpsid i | 禁止IRQ中斷 |
cpsie i | 使能IRQ中斷 |
cpsid f | 禁止FIQ中斷 |
cpsid f | 使能FIQ中斷 |
中斷ID使能
GIC寄存器GICD_ISENABLERn和GCID_ICENABLERn來完成外部中斷等使能和禁止。Cortex-A7內核中斷ID只使用了512個,那么就需要512/32=16個寄存器來完成中斷的使能,同樣需要16個GCID_ICENABLERn來禁止中斷。其中,R0的bit[15:0]對應ID15~0的SGI中斷,剩下的R1~R15就控制了區域的SPI中斷。
優先級設置
和STM32,Cortex-A7分可以配置的搶占優先級和子優先級,並且支持256個優先級,數字越小優先級越高。I.MX6U提供了32個優先級,在使用中斷等時候需要對GICC_PMR寄存器進行初始化,來定義使用幾級優先級。該寄存器只有低8位有效,最多可以設置256個優先級
GICC_PMR的地址也是基於CPU Interface的地址偏移里0x4的。
要注意的是,這256個優先級不是按照8位2進制的數據模式換算的。對不同bit置零,可以指定定使用了多少個優先級。針對I.MX6U來說,支持32個優先級,所以GICC_BPR設置為0b11111000。
搶占優先級和子優先級
搶占優先級比子優先級的優先權更高,這意味搶占優先級更高的中斷會先執行,而不管子優先級的優先權,數值越低優先級越高。同理,如果搶占優先級相同,那么就會比較子優先級,子優先級更高的中斷將會先被執行,數值越低優先級越高。當兩個中斷源的搶占式優先級相同時,這兩個中斷將沒有嵌套關系,當一個中斷到來后,如果正在處理另一個中斷,這個后到來的中斷就要等到前一個中斷處理完之后才能被處理。如果這兩個中斷同時到達,則中斷控制器根據他們的響應優先級高低來決定先處理哪一個;如果他們的搶占式優先級和響應優先級都相等,則根據他們在中斷表中的排位順序決定先處理哪一個。搶占優先級和子優先級各占多少為是由寄存器GICC_BPR決定的,GICC_BPR的低三位決定了搶占優先級和子優先級的位數。
為了簡單化,我們一般把所有的中斷優先級都配置為搶占優先級,比如I.MX6U的優先級位數為5位所以可以把Binary Point設置為2,表示5個優先級位全部為搶占優先級。(說實話這里暫時還不太清楚!)
優先級設置
前面已經定了I.MX6U有32個搶占優先級,數字越小優先級越高,具體使用某個中斷就可以將其優先級設置為0~31,某個中斷的優先級配有一個優先級寄存器DIPRIORITYR來完成,所以512個中斷就對應有512個D_IPRIORITRY寄存器,如果優先級個數為32的話,使用D_IPRIORITRY的bit[7:4]來設置優先級,也就是說實際的優先級要左移3位,比如要設置ID40的優先級為5,代碼要這么寫
GICD_IPRIORITYR[40]=5<<3
中斷優先級的設置還是有些不清楚,大概先講這么多,后面結合代碼來看應該能清除一些!