ARM Cortex-A7中断系统基础知识
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中非常重要,必须深刻理解。