Cortex-A7 IO 输入中断系统分析

Cortex-A7中断向量表是在代码的最前面,Cortex-A7 内核有 8 个异常中断,这 8 个异常中断的中断向量表如表所示:

Cortex-A7 IO 输入中断系统分析

Cortex-A7 中断向量表

中断向量表里面都是中断服务函数的入口地址,因此一款芯片有什么中断都是可以从中断向量表看出来的。从表中可以看出,Cortex-A7 一共有 8 个中断,而且还有一个中断向量未使用,实际只有 7 个中断。和“示例代码 17.1.1.1”中的 STM32F103 中断向量表比起来少了很多!难道一个能跑 Linux 的芯片只有这 7 个中断?明显不可能的!那类似 STM32 中的EXTI9_5_IRQHandler、TIM2_IRQHandler 这样的中断向量在哪里?I2C、SPI、定时器等等的中断怎么处理呢?这个就是 Cortex-A 和 Cotex-M 在中断向量表这一块的区别,对于 Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 Cotex-A 内核来说并没有这么做,在表中有个 IRQ 中断, Cortex-A 内核 CPU 的所有外部中断都属于这个 IQR 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。这些外部中断和 IQR 中断的关系如图所示:

Cortex-A7 IO 输入中断系统分析

外部中断和 IRQ 中断关系

在图中,左侧的 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 文件最前面,中断向量表如下:

1 .global _start /* 全局标号 */

2

3 _start:

4 ldr pc, =Reset_Handler /* 复位中断 */

5 ldr pc, =Undefined_Handler /* 未定义指令中断 */

6 ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */

7 ldr pc, =PrefAbort_Handler /* 预取终止中断 */

8 ldr pc, =DataAbort_Handler /* 数据终止中断 */

9 ldr pc, =NotUsed_Handler /* 未使用中断 */

10 ldr pc, =IRQ_Handler /* IRQ 中断 */

11 ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */

12

13 /* 复位中断 */

14 Reset_Handler:


15 /* 复位中断具体处理过程 */

16

17 /* 未定义中断 */

18 Undefined_Handler:

19 ldr r0, =Undefined_Handler

20 bx r0

21

22 /* SVC 中断 */

23 SVC_Handler:

24 ldr r0, =SVC_Handler

25 bx r0

26

27 /* 预取终止中断 */

28 PrefAbort_Handler:

29 ldr r0, =PrefAbort_Handler

30 bx r0 31

32 /* 数据终止中断 */

33 DataAbort_Handler:

34 ldr r0, =DataAbort_Handler

35 bx r0

36

37 /* 未使用的中断 */

38 NotUsed_Handler:

39

40 ldr r0, =NotUsed_Handler

41 bx r0 42

43 /* IRQ 中断!重点!!!!! */

44 IRQ_Handler:

45 /* 复位中断具体处理过程 */

46

47 /* FIQ 中断 */

48 FIQ_Handler:

49 ldr r0, =FIQ_Handler

50 bx r0

第 4 到 11 行是中断向量表,当指定的中断发生以后就会调用对应的中断复位函数,比如复位中断发生以后就会执行第 4 行代码,也就是调用函数 Reset_Handler,函数 Reset_Handler就是复位中断的中断复位函数,其它的中断同理。

第 14 到 50 行就是对应的中断服务函数,中断服务函数都是用汇编编写的,我们实际需要编写的只有复位中断服务函数Reset_Handler 和 IRQ 中断服务函数 IRQ_Handler,其它的中断本教程没有用到,所以都是死循环。在编写复位中断复位函数和 IRQ 中断服务函数之前我们还需要了解一些其它的知识,否则的话就没法编写。

I.MX6U(Cortex-A)的中断控制器叫做 GIC,关于GIC 的详细内容请参考文档《ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf》。

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,他们之间的关系如图所示:

Cortex-A7 IO 输入中断系统分析

中断示意图

在图中,GIC 接收众多的外部中断,然后对齐进行处理,最终就只通过四个信号报给ARM 内核,这四个信号的含义如下:

VFIQ:虚拟快速 FIQ。

VIRQ:虚拟快速 IRQ。

FIQ:快速中断 IRQ。

IRQ:外部中断 IRQ。

VFIQ 和 VIRQ 是针对虚拟化的,我们讨论虚拟化,剩下的就是 FIQ 和 IRQ 了,我们前面都讲了很多次了。本教程我们只使用 IRQ,所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ信号。那么GIC 是如何完成这个工作的呢?GICV2 的逻辑图如图所示:


Cortex-A7 IO 输入中断系统分析

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 中断来完成多核之间的通信。

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 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 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断 ,至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160个,这 128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节,中断源如表所示:

Cortex-A7 IO 输入中断系统分析

I.MX6U 中断源

限于篇幅原因,表中并没有给出 I.MX6U 完整的中断源,完整的中断源自行查阅《I.MX6ULL 参考手册》的 3.2 小节。打开裸机例程“9_int”,我们前面移植了 NXP 官方 SDK中的文件 MCIMX6Y2C.h,在此文件中定义了一个枚举类型 IRQn_Type,此枚举类型就枚举出了 I.MX6U 的所有中断,代码如下所示:

1 #define NUMBER_OF_INT_VECTORS 160 /* 中断源 160 个,SGI+PPI+SPI*/

2

3 typedef enum IRQn {

4 /* Auxiliary constants */

5 NotAvail_IRQn = -128,

6

7 /* Core interrupts */

8 Software0_IRQn = 0,

9 Software1_IRQn = 1,

10 Software2_IRQn = 2,

11 Software3_IRQn = 3,

12 Software4_IRQn = 4,

13 Software5_IRQn = 5,

14 Software6_IRQn = 6,

15 Software7_IRQn = 7,

16 Software8_IRQn = 8,

17 Software9_IRQn = 9,

18 Software10_IRQn = 10,

19 Software11_IRQn = 11,

20 Software12_IRQn = 12,

21 Software13_IRQn = 13,

22 Software14_IRQn = 14,

23 Software15_IRQn = 15,

24 VirtualMaintenance_IRQn = 25,

25 HypervisorTimer_IRQn = 26,


26 VirtualTimer_IRQn = 27,

27 LegacyFastInt_IRQn = 28,

28 SecurePhyTimer_IRQn = 29,

29 NonSecurePhyTimer_IRQn = 30,

30 LegacyIRQ_IRQn = 31, 31

32 /* Device specific interrupts */

33 IOMUXC_IRQn = 32,

34 DAP_IRQn = 33,

35 SDMA_IRQn = 34,

36 TSC_IRQn = 35,

37 SNVS_IRQn = 36,

…… ...... ......

151 ENET2_1588_IRQn = 153,

152 Reserved154_IRQn = 154,

153 Reserved155_IRQn = 155,

154 Reserved156_IRQn = 156,

155 Reserved157_IRQn = 157,

156 Reserved158_IRQn = 158,

157 PMU_IRQ2_IRQn = 159 158
} IRQn_Type;

3、GIC 逻辑分块

GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。这两个逻辑块的含义如下:

Distributor(分发器端):从图中可以看出,此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下:

①、全局中断使能控制。

②、控制每一个中断的使能或者关闭。

③、设置每个中断的优先级。

④、设置每个中断的目标处理器列表。

⑤、设置每个外部中断的触发模式:电平触发或边沿触发。

⑥、设置每个中断属于组 0 还是组 1。

CPU Interface(CPU 接口端):CPU 接口端听名字就知道是和CPU Core 相连接的,因此在图中每个CPU Core 都可以在GIC 中找到一个与之对应的CPU Interface。CPU 接口端就是分发器和CPU Core 之间的桥梁,CPU 接口端主要工作如下:

①、使能或者关闭发送到CPU Core 的中断请求信号。

②、应答中断。

③、通知中断处理完成。

④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。

⑤、定义抢占策略。

⑥、当多个中断到来的时候,选择优先级最高的中断通知给CPU Core。

文件 core_ca7.h 定义了GIC 结构体,此结构体里面的寄存器分为了分发器端和CPU 接口端,寄存器定义如下所示:

/*

* GIC 寄存器描述结构体,

* GIC 分为分发器端和 CPU 接口端

*/

1 typedef struct 2 {

3 /* 分发器端寄存器 */

4 uint32_t RESERVED0[1024];

5 IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */

6 IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */

7 IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */

8 uint32_t RESERVED1[29];

9 IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */

10 uint32_t RESERVED2[16];

11 IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */

12 uint32_t RESERVED3[16];

13 IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */

14 uint32_t RESERVED4[16];

15 IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */

16 uint32_t RESERVED5[16];

17 IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */

18 uint32_t RESERVED6[16];


19 IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */

20 uint32_t RESERVED7[16];

21 IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */

22 uint32_t RESERVED8[16];

23 IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */

24 uint32_t RESERVED9[128];

25 IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */

26 uint32_t RESERVED10[128];

27 IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */

28 uint32_t RESERVED11[32];

29 IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */

30 IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */

31 uint32_t RESERVED12[112];

32 OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */

33 uint32_t RESERVED13[3];

34 IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */

35 IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */

36 uint32_t RESERVED14[40];

37 IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */

38 IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */

39 IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */

40 IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */

41 IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */

42 IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */

43 IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */


44 IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */

45 IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */

46 IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */

47 IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */

48 IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */ 49

50 /* CPU 接口端寄存器 */

51 IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */

52 IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */

53 IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */

54 IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */

55 OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */

56 IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */

57 IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */

58 IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */

59 IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */

60 OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */

61 IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */

62 uint32_t RESERVED15[41];

63 IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */

64 uint32_t RESERVED16[3];

65 IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */

66 uint32_t RESERVED17[6];

67 IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */

68 uint32_t RESERVED18[960];

69 OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */


70 } GIC_Type;

代码中的结构体 GIC_Type 就是 GIC 控制器,列举除了GIC 控制器的所有寄存器,可以通过结构体 GIC_Type 来访问GIC 的所有寄存器。

第 5 行是 GIC 的分发器端相关寄存器,其相对于GIC 基地址偏移为 0X1000,因此我们获取到GIC 基地址以后只需要加上 0X1000 即可访问GIC 分发器端寄存器。

第 51 行是GIC 的CPU 接口端相关寄存器,其相对于 GIC 基地址的偏移为 0X2000,同样的,获取到 GIC 基地址以后只需要加上 0X2000 即可访问 GIC 的 CPU 接口段寄存器。

那么问题来了?GIC 控制器的寄存器基地址在哪里呢?这个就需要用到 Cortex-A 的 CP15 协处理器了。关于CP15 协处理器和其相关寄存器的详细内容请参考下面两份文档:

《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第 1469 页“B3.17 Oranization of the CP15 registers in a VMSA implementation”。

《Cortex-A7 Technical ReferenceManua.pdf》第 55 页“Capter 4 System Control”。

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有16 个 32 位寄存器。CP15 协处理器的访问通过如下另个指令完成:

MRC: 将CP15 协处理器中的寄存器数据读到 ARM 寄存器中。

MCR: 将ARM 寄存器的数据写入到 CP15 协处理器寄存器中。

MRC 就是读CP15 寄存器,MCR 就是写 CP15 寄存器,MCR 指令格式如下:

MCR{cond} p15, <opc1>, , , , <opc2> /<opc2>/<opc1>

cond:指令执行的条件码,如果忽略的话就表示无条件执行。

opc1:协处理器要执行的操作码。

Rt:ARM 源寄存器,要写入到CP15 寄存器的数据就保存在此寄存器中。

CRn:CP15 协处理器的目标寄存器。

CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将

CRm 设置为C0,否则结果不可预测。

opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。

MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中Rt 就是目标寄存器,也就是从

CP15 指定寄存器读出来的数据会保存在Rt 中。而CRn 就是源寄存器,也就是要读取的写处理器寄存器。

假如我们要将CP15 中C0 寄存器的值读取到R0 寄存器中,那么就可以使用如下命令:

MRC p15, 0, r0, c0, c0, 0

CP15 协处理器有 16 个 32 位寄存器,c0~c15,本章来看一下c0、c1、c12 和 c15 这四个寄存器,因为我们本章实验要用到这四个寄存器,其他的寄存器大家参考上面的两个文档即可。

1、c0 寄存器

CP15 协处理器有 16 个 32 位寄存器,c0~c15,在使用 MRC 或者 MCR 指令访问这 16 个寄存器的时候,指令中的CRn、opc1、CRm 和opc2

通过不同的搭配,其得到的寄存器含义是不同的。比如c0 在不同的搭配情况下含义如图所示:


Cortex-A7 IO 输入中断系统分析

c0 寄存器不同搭配含义

在图中当 MRC/MCR 指令中的CRn=c0,opc1=0,CRm=c0,opc2=0 的时候就表示此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器,这个也是 c0 的基本作用。对于Cortex-A7内核来说,c0 作为 MDIR 寄存器的时候其含义如图所示:

Cortex-A7 IO 输入中断系统分析

c0 作为 MIDR 寄存器结构图

在图中各位所代表的含义如下:

bit31:24:厂商编号,0X41,ARM。

bit23:20:内核架构的主版本号,ARM 内核版本一般使用 rnpn 来表示,比如 r0p1,其中 r0后面的 0 就是内核架构主版本号。

bit19:16:架构代码,0XF,ARMv7 架构。

bit15:4:内核版本号,0XC07,Cortex-A7 MPCore 内核。

bit3:0:内核架构的次版本号,rnpn 中的 pn,比如 r0p1 中 p1 后面的 1 就是次版本号。

2、c1 寄存器

c1 寄存器同样通过不同的配置,其代表的含义也不同,如图所示:

Cortex-A7 IO 输入中断系统分析

c1 寄存器不同搭配含义

在图中当 MRC/MCR 指令中的CRn=c1,opc1=0,CRm=c0,opc2=0 的时候就表示此时的 c1 就是 SCTLR 寄存器,也就是系统控制寄存器,这个是 c1 的基本作用。SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止MMU、I/D Cache 等,c1 作为 SCTLR 寄存器的时候其含义如图所示:

Cortex-A7 IO 输入中断系统分析

c1 作为 SCTLR 寄存器结构图

SCTLR 的位比较多,我们就只看本章会用到的几个位:

bit13:V , 中断向量表基地址选择位,为 0 的话中断向量表基地址为 0X00000000,软件可以使用 VBAR 来重映射此基地址,也就是中断向量表重定位。为 1 的话中断向量表基地址为0XFFFF0000,此基地址不能被重映射。

bit12:I,I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache。

bit11:Z,分支预测使能位,如果开启 MMU 的话,此为也会使能。

bit10:SW,SWP 和 SWPB 使能位,当为 0 的话关闭 SWP 和 SWPB 指令,当为 1 的时候就使能 SWP 和 SWPB 指令。

bit9:3:未使用,保留。

bit2:C,D Cache 和缓存一致性使能位,为 0 的时候禁止 D Cache 和缓存一致性,为 1 时使能。

bit1:A,内存对齐检查使能位,为 0 的时候关闭内存对齐检查,为 1 的时候使能内存对齐检查。

bit0:M,MMU 使能位,为 0 的时候禁止 MMU,为 1 的时候使能MMU。如果要读写 SCTLR 的话,就可以使用如下命令:

MRC p15, 0, , c1, c0, 0 ;读取 SCTLR 寄存器,数据保存到 Rt 中。

MCR p15, 0, , c1, c0, 0 ;将 Rt 中的数据写到 SCTLR(c1)寄存器中。

2、c12 寄存器

c12 寄存器通过不同的配置,其代表的含义也不同,如图所示:

Cortex-A7 IO 输入中断系统分析

c12 寄存器不同搭配含义

在图中当 MRC/MCR 指令中的 CRn=c12,opc1=0,CRm=c0,opc2=0 的时候就表示此时 c12 为VBAR 寄存器,也就是向量表基地址寄存器。设置中断向量表偏移的时候就需要将新的中断向量表基地址写入 VBAR 中,比如在前面的例程中,代码链接的起始地址为0X87800000,而中断向量表肯定要放到最前面,也就是 0X87800000 这个地址处。所以就需要设置VBAR 为 0X87800000,设置命令如下:

ldr r0, =0X87800000 ; r0=0X87800000

MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中,即 c12=0X87800000

3、c15 寄存器

c15 寄存器也可以通过不同的配置得到不同的含义,参考文档《Cortex-A7 Technical ReferenceManua.pdf》第 68 页“4.2.16 c15 registers”,其配置如图所示:

Cortex-A7 IO 输入中断系统分析

c15 寄存器不同搭配含义

在图中,我们需要 c15 作为 CBAR 寄存器,因为GIC 的基地址就保存在CBAR中,我们可以通过如下命令获取到GIC 基地址:

MRC p15, 4, r1, c15, c0, 0 ; 获取GIC 基础地址,基地址保存在 r1 中。 获取到 GIC 基地址以后就可以设置GIC 相关寄存器了,比如我们可以读取当前中断 ID,当前中断 ID 保存在GICC_IAR 中,寄存器GICC_IAR 属于 CPU 接口端寄存器,寄存器地址相对于 CPU 接口端起始地址的偏移为 0XC,因此获取当前中断 ID 的代码如下:

MRC p15, 4, r1, c15, c0, 0 ;获取GIC 基地址

ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址

LDR r0, [r1, #0XC] ;读取 CPU 接口端起始地址+0XC 处的寄存器值,也就是寄存器

;GIC_IAR 的值

关于CP15 协处理器就讲解到这里,简单总结一下,通过 c0 寄存器可以获取到处理器内核信息;通过c1 寄存器可以使能或禁止 MMU、I/D Cache 等;通过 c12 寄存器可以设置中断向量偏移;通过c15 寄存器可以获取 GIC 基地址。关于CP15 的其他寄存器,大家自行查阅本节前面列举的 2 份ARM 官方资料。

中断使能包括两部分,一个是 IRQ 或者 FIQ 总中断使能,另一个就是 ID0~ID1019 这 1020个中断源的使能。

1、IRQ 和 FIQ 总中断使能

IRQ 和 FIQ 分别是外部中断和快速中断的总开关,就类似家里买的进户总电闸,然后ID0~ID1019 这 1020 个中断源就类似家里面的各个电器开关。要想开电视,那肯定要保证进户总电闸是打开的,因此要想使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断(本教程不使用FIQ)。在“6.3.2 程序状态寄存器”小节已经讲过了,寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和禁止,图表所示:

Cortex-A7 IO 输入中断系统分析

开关中断指令

2、ID0~ID1019 中断使能和禁止

GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能,那么就需要 512/32=16 个 GICD_ISENABLER 寄存器来完成中断的使能。同理,也需要 16 个GICD_ICENABLER 寄存器来完成中断的禁止。其中 GICD_ISENABLER0 的 bit[15:0]对应ID15~0 的 SGI 中断,GICD_ISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。剩下GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

学过 STM32 都知道 Cortex-M 的中断优先级分为抢占优先级和子优先级,两者是可以配置的。同样的 Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置的。Cortex-A7 最多可以支持 256 个优先级,数字越小,优先级越高!半导体厂商自行决定选择多少个优先级。I.MX6U 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级,寄存器结构如图所示:

Cortex-A7 IO 输入中断系统分析

GICC_PMR 寄存器

GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,其他优先级数设置如表所示:

Cortex-A7 IO 输入中断系统分析

优先级数设置

I.MX6U 支持 32 个优先级,所以GICC_PMR 要设置为 0b11111000。

2、抢占优先级和子优先级位数设置

抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的,GICC_BPR 寄存器结构如图所示:

Cortex-A7 IO 输入中断系统分析

GICC_BPR 寄存器结构图

寄存器GICC_BPR 只有低3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同,配置如表 所示:

Cortex-A7 IO 输入中断系统分析

GICC_BPR 配置表

为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。

3、优先级设置

前面已经设置好了 I.MX6U 一共有 32 个抢占优先级,数字越小优先级越高。具体要使用某个中断的时候就可以设置其优先级为 0~31 。某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,前面说了 Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话,使用寄存器D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5,示例代码如下:GICD_IPRIORITYR[40] = 5 << 3;

有关优先级设置的内容就讲解到这里,优先级设置主要有三部分:

①、设置寄存器 GICC_PMR,配置优先级个数,比如 I.MX6U 支持 32 级优先级。

②、设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。

③、设置指定中断 ID 的优先级,也就是设置外设优先级。


分享到:


相關文章: