1、前言
中斷系統是嵌入式處理器的重要組成部分,實時控制、異常自動處理、SoC與外圍設備間的數據傳輸往往需要采用中斷系統,中斷系統的應用能夠大大提高處理器的效率。中斷是實現多道程序設計的必要條件,它是處理器對系統發生的某個事件作出的一種反應,引起中斷的事件叫做中斷源,中斷源向處理器提出處理的請求稱為中斷請求,發生中斷時被打斷程序的暫停點叫做斷點,處理器暫停當前運行的程序而轉為響應中斷請求的過程稱為中斷響應,處理中斷源的程序稱為中斷服務處理程序,處理器執行有關的中斷服務處理程序稱為中斷處理,斷點的返回過程稱為中斷返回,中斷系統的實現需要軟件和硬件綜合完成。
2、ARM Cortex-A7中斷系統
了解和使用過MCU的人都知道中斷向量表,它存放了一系列的中斷向量,也就是中斷服務處理程序的入口地址,中斷向量表的位置都是由半導體廠商定義好的,當某個異常或者中斷被觸發以后,處理器會自動跳轉到中斷向量表中對應的中斷服務處理程序入口地址,執行相應中斷服務處理程序,中斷向量表往往存在整個應用程序的最前面,對於ARM v7架構的嵌入式SoC也有對應的中斷向量表,如下所示:

需要注意的是,Hyp這一列定義了Hyp模式中斷向量表入口,Monitor這一列定義了Monitor模式中斷向量表入口。
中斷向量表中存放的就是中斷服務處理函數的入口地址,從表中可以看到,對於ARM v7架構的芯片有8個中斷入口地址,對於ARM Cortex-M系列的芯片內核來說,往往是在中斷向量表中列舉出一款芯片的所有中斷入口地址,例如:GPIO、UART和定時器等中斷處理服務函數入口地址,但是對於ARM Cortex-A系列的SoC來說,則是有一定的區別,從上面的中斷向量表中可以看到IRQ interrupt,ARM Cortex-A系列的SoC上的外設中斷都屬於這個IRQ interrupt,芯片內部的中斷控制器使用了中斷號的機制,每個中斷源對應着一個中斷號,當任意一個中斷發生后,都會觸發IRQ interrupt,然后調用IRQ interupt的中斷服務處理函數,在該函數中就可以讀取指定的寄存器獲取中斷號,然后判斷所發生的具體中斷,從而做出相應的中斷處理。
對於ARM v7架構的CPU最多支持的中斷號可以到達1020個,為ID0~ID1019,但是對於實際的芯片廠商並不會全部使用完這些中斷號,例如:對於NXP研發的I.MX6UL芯片就只使用了160個中斷號,為ID0~ID159,其中前32個用於SoC內核私有,並沒有用到芯片外設上,ID32~ID159這些中斷號就使用到了芯片外設上,部分IRQ號和中斷源的描述如下:

比如說,中斷號ID39就被用在了CSI外設接口中,這些外設中斷與中斷向量表中的IRQ interrupt關系如下:

接下來,簡單介紹中斷向量表中的8個中斷,如下:
- Reset:復位中斷,嵌入式SoC復位以后進入到復位中斷,可以在復位中斷函數里面進行一些相關的初始化工作,例如初始化SP、DDR等外設等;
- Undefined Instruction:未定義指令中斷,當處理器不能識別指令的話將會產生該中斷;
- Supervisor Call:SVC指令中斷,Supervisor用戶調用SVC指令請求Supervisor功能,處理器將進入到超級用戶模式,通常用來請求操作系統功能,對於以前的ARM體系架構,SVC指令也叫做SWI,即軟件中斷;
- Prefetch Abort:指令預取中止中斷,當處理器預取指令出錯的時候將會產生此中斷;
- Data Abort:數據訪問中止中斷,當處理器訪問數據出錯的時候會產生此種斷;
- Not used:未使用中斷;
- IRQ Interrupt:IRQ中斷,當芯片的外設發生中斷就會引起該中斷的產生;
- FIQ Interrupt:FIQ中斷,快速中斷,當想要快速處理中斷的話可以使用該中斷。
在上述列出的8個中斷中,在ARM裸機開發中使用得較多的就是Reset Interrupt和IRQ Interrupt,所以需要注意這兩個中斷處理服務函數的編寫,中斷向量表往往處於程序最開始的地方,ARM Cortex-A系列的中斷向量表簡單模板如下所示:
.global _start /* * _start函數,程序先在這開始執行 */ _start: /* 創建中斷向量表 */ ldr pc, =Reset_Handler /* 復位中斷 */ ldr pc, =Undefined_Handler /* 未定義指令中斷 */ ldr pc, =SVC_Handler /* 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: /* 進行一些初始化處理,初始化SP指針,初始C運行環境 */ /* 跳轉到C語言的main函數執行 */ /* 未定義指令中斷函數 */ Undefined_Handler: ldr r0, =Undefined_Handler bx r0 /* Supervisor中斷服務函數 */ 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: /* 保護中斷現場 */ /* 跳轉到C版本的中斷服務處理函數 */ /* 恢復中斷現場 */ /* FIQ中斷服務函數 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0
中斷處理服務函數都是使用匯編函數進行編寫,對於IRQ Interrupt中斷服務處理函數,是最重要的,IRQ中斷發生后,我們需要將中斷現場進行保護,然后跳轉到C版本的中斷處理服務函數進行執行,主要是獲取具體的中斷ID,然后通過中斷ID號去判斷是什么芯片外設產生了中斷,進行相應的處理,中斷處理完后,需要恢復中斷現場,回到斷點處繼續執行原來的程序。
3、GIC中斷控制器
ARM v7-A內核架構系列的SoC使用GIC來進行中斷的管理,GIC的全稱為Generic Interrupt Controller,中文名稱為通用的中斷控制器,是ARM進行開發的,目前GIC有4個版本,為V1~V4,其中GIC V2是給ARM v7-A架構的芯片使用的,例如:ARM Cortex-A7內核的SoC。
GIC中斷控制器用來管理處理器系統的所有中斷源,適用於單處理器或多處理器的系統,它支持以下相關功能:
- ARM體系結構的安全拓展;
- ARM體系結構的虛擬化拓展;
- 啟用或者禁用外圍的中斷源,還能產生處理器中斷;
- 軟件產生中斷(SGI);
- 支持中斷屏蔽和中斷優先級設置;
- 支持單個處理器或者多個處理器;
- 電源管理環境下的喚醒事件支持。
當GIC接收到芯片外部的中斷信號后,就會上報到ARM Core,但是ARM Core只是提供了4個信號來給GIC匯報中斷的情況,分別為:VFIQ、VIRQ、FIQ和IRQ,其中VFIQ和VIRQ是針對虛擬化,暫時不關注,FIQ是用於快速中斷的,重點關注IRQ,GIC和ARM Core中斷匯報關系如下:

對GIC管理中斷源的大概機制有一定了解后,接下來,需要進一步分析GIC的框架,主要分為兩個部分,分別為Distributor和CPU interfaces,GIC的框架示意圖如下所示:

在上面圖中,可以看到,最左側的就是中斷源,中間就是GIC的內部框圖,最右側是GIC向ARM Core進行中斷信息匯報,GIC中斷控制器將所有的中斷源分成3類,分別為SGI、PPI和SPI,解析如下:
- SGI:全稱Software Generated Interrupt,由軟件觸發產生的中斷,可以通過向GICD_SGIR寄存器中寫入數據進行觸發,該中斷通常用於內核間的通信;
- PPI:全稱Private Peripheral Interrupt,私有外設中斷,GIC是支持多核心的CPU的,每個核心都有自己獨有的中斷,例如:每個核心的timer中斷;
- SPI:全稱Shared Peripheral Interrupt,共享外設中斷,所有的ARM Core共享的中斷,由SoC的外圍設備所產生的中斷。
芯片的所有中斷源被分成了3大類,可是中斷源哪么多,GIC是怎么區分的呢?主要是通過Interrupt ID機制,GIC為每個中斷源都分配一個ID號,每個CPU Interface最多支持1020個中斷ID,為ID0~ID1019,分配如下:
- ID0~ID15:這16個Interrupt ID分配給了SGI,存在於GIC的Distributor中;
- ID16~ID31:這16個Interrupt ID分配給了PPI,同樣存在於GIC的Distributor中;
- ID32~ID1019:這988個Interrupt ID分配給了SPI,用於SoC的外圍設備中斷,至於用多少個ID,然后外圍設備怎么用,就要看各個芯片廠商根據實際情況去定義了。
在上面已經提及到了GIC的架構,主要分為兩個部分,分別為Distributor和CPU interfaces,接下來看看這兩部分要做的工作:
首先是Distributor,主要是集中所有的中斷源,負責各個中斷的分發問題,確定每個中斷的優先級,確定每個CPU Interface的優先級,將優先級最高的中斷轉發到CPU Interface,以進行優先級屏蔽和搶占處理,Distributor提供了用於以下目的的編程接口:
- 全局中斷使能的控制;
- 控制每一個中斷的啟用和禁止;
- 設置每個中斷的優先級;
- 設置每個中斷的目標處理器列表;
- 設置每個中斷的觸發方式,電平觸發或者邊沿觸發;
- 將每個中斷設置為group 0或者group 1。
然后就是CPU interfaces,該部分是和CPU Core進行連接,在每個CPU Core都可以找到一個對應的CPU Interface,它是Distributor與CPU Core之間的橋梁,提供了用於以下目的的編程接口:
- 啟用或者禁用發送到CPU Core的中斷請求信號;
- 應答中斷;
- 指示中斷已經處理完成;
- 為處理器設置中斷優先級掩碼;
- 定義處理器的搶占策略;
- 多個中斷到來時,選擇優先級最高的中斷通知給CPU Core。
GIC的相關寄存器是存儲器映射的,它的基地址是由PERIPHBASE[39:15]所指定的,PERIPHBASE的值可以通過讀取CP15協處理器的CBAR寄存器可以得到,GIC的內存地址映射圖如下所示:

從上圖可以看到,相對於GIC的基地址偏移0x0000~0x0FFF為系統保留,地址偏移0x1000~0x1FFF為GIC的Distributor相關寄存器地址,地址偏移0x2000~0x3FFF為GIC的CPU Interface相關寄存器地址,如果使用C語言的結構體類型對GIC的所有寄存器進行封裝的話,如下所示:
typedef struct { uint32_t RESERVED0[1024]; __IOM uint32_t D_CTLR; /*!< Offset: 0x1000 (R/W) Distributor Control Register */ __IM uint32_t D_TYPER; /*!< Offset: 0x1004 (R/ ) Interrupt Controller Type Register */ __IM uint32_t D_IIDR; /*!< Offset: 0x1008 (R/ ) Distributor Implementer Identification Register */ uint32_t RESERVED1[29]; __IOM uint32_t D_IGROUPR[16]; /*!< Offset: 0x1080 - 0x0BC (R/W) Interrupt Group Registers */ uint32_t RESERVED2[16]; __IOM uint32_t D_ISENABLER[16]; /*!< Offset: 0x1100 - 0x13C (R/W) Interrupt Set-Enable Registers */ uint32_t RESERVED3[16]; __IOM uint32_t D_ICENABLER[16]; /*!< Offset: 0x1180 - 0x1BC (R/W) Interrupt Clear-Enable Registers */ uint32_t RESERVED4[16]; __IOM uint32_t D_ISPENDR[16]; /*!< Offset: 0x1200 - 0x23C (R/W) Interrupt Set-Pending Registers */ uint32_t RESERVED5[16]; __IOM uint32_t D_ICPENDR[16]; /*!< Offset: 0x1280 - 0x2BC (R/W) Interrupt Clear-Pending Registers */ uint32_t RESERVED6[16]; __IOM uint32_t D_ISACTIVER[16]; /*!< Offset: 0x1300 - 0x33C (R/W) Interrupt Set-Active Registers */ uint32_t RESERVED7[16]; __IOM uint32_t D_ICACTIVER[16]; /*!< Offset: 0x1380 - 0x3BC (R/W) Interrupt Clear-Active Registers */ uint32_t RESERVED8[16]; __IOM uint8_t D_IPRIORITYR[512]; /*!< Offset: 0x1400 - 0x5FC (R/W) Interrupt Priority Registers */ uint32_t RESERVED9[128]; __IOM uint8_t D_ITARGETSR[512]; /*!< Offset: 0x1800 - 0x9FC (R/W) Interrupt Targets Registers */ uint32_t RESERVED10[128]; __IOM uint32_t D_ICFGR[32]; /*!< Offset: 0x1C00 - 0xC7C (R/W) Interrupt configuration registers */ uint32_t RESERVED11[32]; __IM uint32_t D_PPISR; /*!< Offset: 0x1D00 (R/ ) Private Peripheral Interrupt Status Register */ __IM uint32_t D_SPISR[15]; /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared Peripheral Interrupt Status Registers */ uint32_t RESERVED12[112]; __OM uint32_t D_SGIR; /*!< Offset: 0x1F00 ( /W) Software Generated Interrupt Register */ uint32_t RESERVED13[3]; __IOM uint8_t D_CPENDSGIR[16]; /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI Clear-Pending Registers */ __IOM uint8_t D_SPENDSGIR[16]; /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI Set-Pending Registers */ uint32_t RESERVED14[40]; __IM uint32_t D_PIDR4; /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 Register */ __IM uint32_t D_PIDR5; /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 Register */ __IM uint32_t D_PIDR6; /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 Register */ __IM uint32_t D_PIDR7; /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 Register */ __IM uint32_t D_PIDR0; /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 Register */ __IM uint32_t D_PIDR1; /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 Register */ __IM uint32_t D_PIDR2; /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 Register */ __IM uint32_t D_PIDR3; /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 Register */ __IM uint32_t D_CIDR0; /*!< Offset: 0x1FF0 (R/ ) Component ID0 Register */ __IM uint32_t D_CIDR1; /*!< Offset: 0x1FF4 (R/ ) Component ID1 Register */ __IM uint32_t D_CIDR2; /*!< Offset: 0x1FF8 (R/ ) Component ID2 Register */ __IM uint32_t D_CIDR3; /*!< Offset: 0x1FFC (R/ ) Component ID3 Register */ __IOM uint32_t C_CTLR; /*!< Offset: 0x2000 (R/W) CPU Interface Control Register */ __IOM uint32_t C_PMR; /*!< Offset: 0x2004 (R/W) Interrupt Priority Mask Register */ __IOM uint32_t C_BPR; /*!< Offset: 0x2008 (R/W) Binary Point Register */ __IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */ __OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */ __IM uint32_t C_RPR; /*!< Offset: 0x2014 (R/ ) Running Priority Register */ __IM uint32_t C_HPPIR; /*!< Offset: 0x2018 (R/ ) Highest Priority Pending Interrupt Register */ __IOM uint32_t C_ABPR; /*!< Offset: 0x201C (R/W) Aliased Binary Point Register */ __IM uint32_t C_AIAR; /*!< Offset: 0x2020 (R/ ) Aliased Interrupt Acknowledge Register */ __OM uint32_t C_AEOIR; /*!< Offset: 0x2024 ( /W) Aliased End Of Interrupt Register */ __IM uint32_t C_AHPPIR; /*!< Offset: 0x2028 (R/ ) Aliased Highest Priority Pending Interrupt Register */ uint32_t RESERVED15[41]; __IOM uint32_t C_APR0; /*!< Offset: 0x20D0 (R/W) Active Priority Register */ uint32_t RESERVED16[3]; __IOM uint32_t C_NSAPR0; /*!< Offset: 0x20E0 (R/W) Non-secure Active Priority Register */ uint32_t RESERVED17[6]; __IM uint32_t C_IIDR; /*!< Offset: 0x20FC (R/ ) CPU Interface Identification Register */ uint32_t RESERVED18[960]; __OM uint32_t C_DIR; /*!< Offset: 0x3000 ( /W) Deactivate Interrupt Register */ } GIC_Type;
通過CBAR寄存器得到了GIC的基地址后,將該地址強制轉換為GIC_Type *類型,我們就可以通過GIC_Type結構體去訪問GIC所有的寄存器,在GIC_Type結構體中,以D_xxx描述的為Distributor相關寄存器,以C_xxx描述的為CPU Interface相關寄存器。
接下來,簡單介紹GIC中一些重要的寄存器,首先是Distributor相關的寄存器:
GICD_CTLR為Distributor控制寄存器,該寄存器用來使能中斷是否從Distributor轉發到CPU Interface,寄存器描述如下:

GICD_ISENABLERn的功能是用來使能SPI和PPI中斷的,該寄存器的描述如下:

對於ARM Cortex-A7內核來說,中斷ID使用了512個,GICD_ISENABLERn寄存器一個bit控制一個中斷ID的使能,哪么就需要512/32=16個這樣的寄存器來控制中斷的使能,GICD_ISENABLER0寄存器的bit[15:0]用來控制ID15~ID0的16個SGI中斷,GICD_ISENABLER0寄存器的bit[31:16]用來控制ID31~ID16的16個PPI中斷,剩下的GICD_ISENABLER1~GICD_ISENABLER15寄存器用來控制SPI中斷使能。
GICD_ICENABLERn的功能是用來禁止SPI和PPI中斷的,和GICD_ISENABLERn類似,該寄存器的描述如下:

GICD_ISENABLERn和GICD_ICENABLERn實現的功能是用來使能和禁止每一個具體的中斷的,每個bit代表一個中斷ID,但是對於FIQ和IRQ的全局使能和禁止可以通過設置程序狀態寄存器CPSR的F和I對應的位,寄存器CPSR的F=1表示禁止FIQ,F=0表示使能FIQ,寄存器CPSR的I=1表示禁止IRQ,I=0表示使能IRQ,另外,還可以使用下面指令完成FIQ和IRQ使能控制:
| 指令 | 功能 |
| cpsid i | 禁止IRQ中斷 |
| cpsie i | 使能IRQ中斷 |
| cpsid f | 禁止FIQ中斷 |
| cpsie f | 使能FIQ中斷 |
接下來繼續介紹CPU Interface相關的一些重要寄存器:
首先是 GICC_PMR寄存器,該寄存器用來設置ARM Cortex-A7內核中斷的優先級的,和STM32內核類似,Cortex-A7內核也是可以配置中斷優先級的搶占優先級和子優先級的,它最多可以支持256個優先級,數字越小,優先級別越高,例如,I.MX6UL這款芯片就選擇了32個優先級,GICC_PMR寄存器的描述如下:

該寄存器只有低8位有效,Priority(bit[7:0])所對應的優先級配置如下:
| bit[7:0] | 優先級 |
| 11111110 | 128個優先級 |
| 11111100 | 64個優先級 |
| 11111000 | 32個優先級 |
| 11110000 | 16個優先級 |
對於I.MX6UL這款芯片支持32個優先級,因此在初始化中斷時需要配置GICC_PMR的bit[7:0]=0b11111000。
另外,搶占優先級和子優先級的配置的位數是由GICC_BPR寄存器進行設定的,該寄存器的描述如下所示:

該寄存器只有Binary point(bit[2:0])有效,這些bit的配置表如下所示:

在進行ARM裸機開發的時候,為簡單起見,一般可以將中斷優先級設置為搶占優先級,例如:I.MX6UL的優先級位數為5,也就是32個優先級等級,可以設置Binary point value為2,則表示5個優先級位全部為搶占優先級,如果設置為32個優先級等級后,使用到某個中斷的時候可以設置優先級為0~31,對於中斷的優先級設置是由寄存器GICD_IPRIORITYRn來完成的,對於Cortex-A7內核來說使用了512個中斷ID,每個中斷ID都有一個GICD_IPRIORITYR寄存器來進行優先級配置,因此一共有512個GICD_IPRIORITYR寄存器,例如,優先級等級個數為32的話,使用GICD_IPRIORITYR寄存器的bit[7:3]來配置中斷ID為40的優先級為5,可以使用下面指令:
GICD_IPRIORITYR[40] = 5 << 3;
其它中斷ID的優先級配置類似,對於設置的數字越小,中斷的優先級越高。
接下來,要介紹的是GICC_IAR寄存器,全稱為Interrupt Acknowledge Register,GICC_IAR是一個只讀寄存器,保存了需要中斷處理的中斷ID號,寄存器的描述如下:

處理器通過讀取Interrupt ID(bit[9:0])可以獲取到信號中斷的中斷ID,讀取該寄存器表示對中斷的確認,在IRQ的中斷服務處理函數中,需要讀取該寄存器,從而判斷出是哪些外設產生了中斷,然后執行相應的處理。
接下來,需要介紹的是GICC_EOIR寄存器,全稱為End of Interrupt Register,GICC_EOIR是一個只寫寄存器,該寄存器的描述如下:

該寄存器主要是通知CPU Interface處理器以完成指定中斷ID的處理,在指定的中斷ID對應的中斷處理服務函數執行完成后,需要寫GICC_EOIR寄存器EOIINTID(bit[9:0])通知CPU Interface已經處理了相應的中斷。
GIC的一些重要寄存器就介紹到這里,關於GIC寄存器的更多描述信息,可以參考ARM文檔《ARM Generic Interrupt Controller V2.0.pdf》。
4、CP15協處理器
CP15是系統控制協處理器,它主要是為處理器中實現的功能提供控制和狀態信息,它包含了16個32bit的寄存器,寄存器被命名為c0~c15,對於CP15寄存器的讀取訪問受特權控制,並且要使用特定的指令進行寄存器的訪問,它實現的主要功能為:
- 系統整體的控制和配置;
- 內存管理單元(MMU)的配置和管理;
- Cache的配置和管理;
- 虛擬化和安全性配置;
- 系統性能的監控。
CP15協處理器中的寄存器訪問是通過下面兩個指令進行完成的:
- MRC,將CP15協處理器中的寄存器數據讀取到ARM寄存器中;
- MCR,將ARM寄存器中的數據寫入到CP15協處理器中。
MRC和MCR指令的格式如下所示:
MRC p15, Op1, Rt, CRn, CRm, Op2 ;read a CP15 register into an ARM register MCR p15, Op1, Rt, CRn, CRm, Op2 ;write a CP15 register from an ARM register
Op1:CP15協處理器要執行的操作碼;
Rt:ARM源寄存器;
CRn:CP15協處理器的目標寄存器;
CRm:CP15協處理器中附加的目標寄存器或者源操作數寄存器,如果不需要附加信息的話可以設置為c0;
Op2:可選的CP15協處理器特定操作碼,如果不需要可以設置為0。
例如,想將CP15協處理器的c0寄存器讀取到ARM寄存器r1,可以使用下面指令:
MRC p15, 0, R1, c0, c0, 0 ;read a CP15 register c0 into an ARM register r1
CP15協處理器相關的寄存器比較多,接下來,了解一些重要的寄存器,例如c0、c1、c12和c15寄存器,這些寄存器在不同的Op1、CRm以及Op2搭配情況下,所表示的含義也不相同。
首先是c0寄存器,不同的搭配情況所表示的含義如下所示:

如上圖所示,如果在使用MRC/MCR命令時,CRn=c0、opc1=0、CRm=c0以及opc2=0,此時c0所代表的寄存器為MDIR,也就是Main ID Register,MDIR寄存器主要用來提供處理器的標識信息,是一個只讀寄存器,寄存器的描述如下所示:

MDIR寄存器相關bit表示含義如下:
Implementer(bit[31:24]):內核廠商編號,0x41表示為ARM;
Variant(bit[23:20]):內核架構的主版本號,對於ARM內核架構一般使用rnpn去表示,比如r0p1,其中r0后面的0就表示為內核架構的主版本號,0x0;
Architecture(bit[19:16]):內核架構代碼,0xF,表示為ARMv7架構;
Primary part number(bit[15:4]):內核的版本號,0xC07,表示為Cortex-A7 MPCore;
Revision(bit[3:0]):內核架構的次版本號,對於ARM內核架構一般使用rnpn去表示,比如r0p1,其中p1后面的1就表示為內核架構的次版本號,為0x5。
讀取MDIR寄存器可以使用下面的指令:
MRC p15, 0, <Rt>, c0, c0, 0 ;read Main ID Register
接下來要介紹的是c1寄存器,和c0類似,不同的搭配情況所表示的寄存器含義如下所示:

如上圖所示,如果在使用MRC/MCR命令時,CRn=c1、opc1=0、CRm=c0以及opc2=0,此時c1所代表的寄存器為SCTLR,也就是System Control Register,SCTLR寄存器的功能主要是提供對系統(包括內存管理系統)的頂層控制,它是一個可讀寫的寄存器,寄存器的描述如下:

SCTLR寄存器bit的含義比較多,分析一些重點的寄存器bit,如下:
V(bit13):中斷向量表基地址選擇位,該bit為0的話,表示為正常的中斷向量表基地址,為0x00000000,軟件可使用VBAR寄存器來重新映射該基地址,該bit為1的話,表示為高的中斷向量表基地址,為0xFFFF0000,該基地址不能夠被映射;
I(bit12):Instruction cache使能位,該bit為0的話,Instruction cache禁止,該bit為1的話,則使能Instruction cache,芯片復位時該bit為0;
Z(bit11):分支預測使能位,如果開啟MMU的話,該bit也會被使能;
SW(bit[10:9]):SW、SWP和SWPB指令使能位,0的話表示關閉,1的話表示開啟相關指令;
C(bit2):Cache使能位,0的話表示Data和unified caches禁止,1的話表示使能Data和unified caches,芯片復位時該bit位0;
A(bit1):內存對齊檢查使能位,0的話表示關閉,1的話表示開啟;
M(bit0):MMU使能位,0的話表示關閉MMU功能,1的話表示開啟MMU。
讀取和寫入SCTLR寄存器,可以使用下面的指令:
MRC p15, 0, <Rt>, c1, c0, 0 ; Read System Control Register MCR p15, 0, <Rt>, c1, c0, 0 ; Write System Control Register
接下來就是c12寄存器,不同的搭配情況所表示的寄存器含義如下所示:

如上圖所示,如果在使用MRC/MCR命令時,CRn=c12、opc1=0、CRm=c0以及opc2=0,此時c12所代表的寄存器為VBAR,也就是Vector Base Address Register,當SCTLR寄存器的中斷向量表基地址選擇位V=0,軟件可以通過VBAR寄存器重新映射該基地址,VBAR是32bit的可讀寫寄存器,其描述如下:

Vector_Base_Address(bit[31:5]):要設置的中斷向量表基地址;
Reserved(bit[4:0]):相對於中斷向量表基地址的中斷向量偏移,保留為0b00000。
讀取和寫入VBAR寄存器的話,可以使用下面的指令:
MRC p15, 0, <Rt>, c12, c0, 0 ; Read VBAR into Rt MCR p15, 0, <Rt>, c12, c0, 0 ; Write Rt to VBAR
最后要分析的就是c15寄存器,不同的搭配情況所表示的寄存器含義如下所示:

如上圖所示,如果在使用MRC/MCR命令時,CRn=c15、opc1=4、CRm=c0以及opc2=0,此時c15所代表的寄存器為CBAR,也就是Configuration Base Address Register,該寄存器是一個只讀寄存器,保存了內存映射的GIC相關寄存器的基地址,CBAR寄存器的描述如下所示:

PERIPHBASE[31:15](bit[31:15]):保存了PERIPHBASE[31:15]的值,表示為GIC的基地址,該值取決於復位值;
Reserved(bit[14:8]):保留;
PERIPHBASE[39:32](bit[7:0]):保存了PERIPHBASE[39:32]的值,該值同樣取決於復位值。
想要讀取CBAR寄存器的話,可以使用下面指令:
MRC p15, 4, <Rt>, c15, c0, 0; Read Configuration Base Address Register
關於CP15協處理器中相關寄存器的更多詳細描述信息,可以參考ARM官方文檔《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》和《Cortex-A7 Technical ReferenceManua.pdf》。
5、中斷處理具體過程
接下來,進行一個最簡單的中斷處理實例分析,該實例代表了最簡單的中斷服務函數處理過程,在一個中斷發生后,其它同類的中斷將被禁止除非隨后被明確使能,只有當第一個中斷請求完成時,我們才能去處理產生的額外中斷,並且在此期間,沒有更高優先級或者更緊急的中斷需要處理,需要注意,該實例通常並不適合復雜的嵌入式系統,但是在進行更深入的中斷研究之前,對該實例的理解將會很有用,在該實例中,中斷處理程序是不可重入的。
中斷處理的具體步驟如下:
(1)外部硬件引發IRQ中斷異常后,ARM Core將會自動執行以下幾個步驟,當前執行模式下PC寄存器的內容將被存儲在LR_IRQ寄存器中,CPSR寄存器中的內容被復制到SPSR_IRQ寄存器中,然后更新CPSR寄存器中的內容,處理器運行模式bit將反映處理器進入了IRQ模式,I位被設置為屏蔽其它的IRQ,PC被設置為中斷異常向量表的IRQ入口。
該執行步驟如下所示:

需要注意,該步驟也稱為保存當前程序的上下文內容,也就是中斷異常發生后,需要保護現場。
(2)向量表中的IRQ中斷處理服務程序的相關指令被執行。
(3)中斷處理程序將被中斷的程序上下文進行保存,也就是將被中斷處理程序破壞的相關寄存器壓入堆棧,當中斷處理程序執行完成后,前面被壓入堆棧的相關寄存器會被彈出,從堆棧中刪除。
該執行步驟如下所示:

(4)在匯編的IRQ中斷處理程序中,必須通過讀取GIC相關的寄存器獲取中斷ID號,然后判斷是哪個外設中斷源引發的中斷異常,並調用相應的驅動處理程序。
(5)響應中斷完成后,需要將SPSR_IRQ寄存器的內容復制到CPSR,准備將ARM Core切換到中斷發生前的執行狀態,恢復先前保存的上下文,最后,通過LR_IRQ寄存器恢復PC。
該執行步驟如下所示:

需要注意,該步驟也稱為恢復中斷現場。
使用匯編代碼編寫一個簡單的匯編IRQ中斷服務處理程序如下:
IRQ_Handler: PUSH {r0-r3, r12, lr} @ Store AAPCS registers and LR onto the IRQ mode stack BL @ identify_and_clear_source BL @ C-irq_handler POP {r0-r3, r12, lr} @ Restore registers and SUBS pc, lr, #4 @ return from exception using modified LR
以上的中斷處理思路同樣適用於FIQ中斷異常處理,中斷處理的具體過程介紹就到這里。
6、小結
本文簡單總結了ARM Cortex-A7中斷系統的一些基礎知識,主要包括中斷的一些基礎概念、ARMv7-A架構內核的中斷異常向量表、GIC中斷控制器基礎知識、CP15協處理的相關寄存器介紹以及ARM中斷處理的具體過程等知識,中斷相關的內容在嵌入式ARM中非常重要,必須深刻理解。
