1 中断系统简介
1.1 中断向量表
中断向量表的定义的就是中断服务程序的跳转指令,因为每个中断向量在向量表中只有一个字节的存储空间,只能存放一条指令,所以通常存放跳转指令,使程序跳转到存储器的其他地方,再执行中断处理。这里cpu就可以找中断服务程序,跳转指令例如:
LDRPC, =ISR_HANDLER
;
Cortex-A7内核有8个异常中断,这8个异常中断的中断向量表如下表所示:
中断向量表里面都是中断服务函数的入口地址,因此一款芯片有什么中断都是可以从中断向量表看出来的。从上表中可以看出,Cortex-A7 一共有 8 个中断,而且还有一个中断向量未使用,实际只有 7 个中断。
下面我们来简单介绍一下这7个中断:
1.复位中断(Rest), CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化SP指针、DDR等等。
2.未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
3.软中断(Software Interrupt,SWI),由SWI指令引起的中断,Linux的系统调用会用SWI指令来引起软中断,通过软中断来陷入到内核空间。
4.指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
5.数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
6.IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
7.FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。
在上面的7个中断中,我们常用的就是复位中断和IRQ中断,所以我们需要编写这两个中断的中断服务函数,稍后我们会讲解如何编写对应的中断服务函数。首先我们要根据上表的内容来创建中断向量表,中断向量表处于程序最开始的地方,比如我们8_int例程的 start.S文件最前面,中断向量表如下:
1
2 .global _start /* 全局标号 */
3
4 /*
5 * 描述:_start函数,首先是中断向量表的创建
6 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,
3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
7 * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
8 */
9 _start:
10 ldr pc, =Reset_Handler /* 复位中断 */
11 ldr pc, =Undefined_Handler /* 未定义中断 */
12 ldr pc, =SVC_Handler /* SVC(Supervisor)中断*/
13 ldr pc, =PrefAbort_Handler /* 预取终止中断 */
14 ldr pc, =DataAbort_Handler /* 数据终止中断 */
15 ldr pc, =NotUsed_Handler /* 未使用中断 */
16 ldr pc, =IRQ_Handler /* IRQ中断 */
17 ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
18
19 /* 复位中断 */
20 Reset_Handler:
21
22 cpsid i /* 关闭全局中断 */
23
24 /* 关闭I,DCache和MMU
25 * 采取读-改-写的方式。
26 */
27 mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中*/
28 bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache*/
29 bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */
30 bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */
31 bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
32 bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */
33 mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入CP15的C1寄存器中*/
34
35
36 #if 0
37 /* 汇编版本设置中断向量表偏移 */
38 ldr r0, =0X87800000
39
40 dsb
41 isb
42 mcr p15, 0, r0, c12, c0, 0
43 dsb
44 isb
45 #endif
46
47 /* 设置各个模式下的栈指针,
48 * 注意:IMX6UL的堆栈是向下增长的!
49 * 堆栈指针地址一定要是4字节地址对齐的!!!
50 * DDR范围:0X80000000~0X9FFFFFFF
51 */
52 /* 进入IRQ模式 */
53 mrs r0, cpsr
54 bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4*/
55 orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
56 msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
57 ldr sp, =0x80600000 /*设置IRQ模式下栈首地址0X80600000,大小为2M*/
58
59 /* 进入SYS模式 */
60 mrs r0, cpsr
61 bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,cpsr的M0~M4 */
62 orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
63 msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
64 ldr sp, =0x80400000 /* 设置SYS模式栈首地址为0X80400000,大小2M*/
65
66 /* 进入SVC模式 */
67 mrs r0, cpsr
68 bic r0, r0, #0x1f /* 将r0寄存器中低5位清零,cpsr的M0~M4 */
69 orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
70 msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
71 ldr sp, =0X80200000 /* 设置SVC模式栈首地址为0X80200000,大小2M*/
72
73 cpsie i /* 打开全局中断 */
74 #if 0
75 /* 使能IRQ中断 */
76 mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */
77 bic r0, r0, #0x80 /* 将r0中bit7清零,也就是CPSR的I位清零,允许IRQ*/
78 msr cpsr, r0 /* 将r0重新写入到cpsr中 */
79 #endif
80
81 b main /* 跳转到main函数 */
82
83 /* 未定义中断 */
84 Undefined_Handler:
85 ldr r0, =Undefined_Handler
86 bx r0
87
88 /* SVC中断 */
89 SVC_Handler:
90 ldr r0, =SVC_Handler
91 bx r0
92
93 /* 预取终止中断 */
94 PrefAbort_Handler:
95 ldr r0, =PrefAbort_Handler
96 bx r0
97
98 /* 数据终止中断 */
99 DataAbort_Handler:
100 ldr r0, =DataAbort_Handler
101 bx r0
102
103 /* 未使用的中断 */
104 NotUsed_Handler:
105
106 ldr r0, =NotUsed_Handler
107 bx r0
108
109 /* IRQ中断!重点!!!!! */
110 IRQ_Handler:
111 push {
lr} /* 保存lr地址 */
112 push {
r0-r3, r12} /* 保存r0-r3,r12寄存器 */
113
114 mrs r0, spsr /* 读取spsr寄存器 */
115 push {
r0} /* 保存spsr寄存器 */
116
117 mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中*/
118
119
120
121 add r1, r1, #0X2000 /* GIC基地址加0X2000,GIC的CPU接口端基地址*/
122 ldr r0, [r1, #0XC] /* GIC的基地址加0X0CGICC_IAR寄存器,*/
123
124
125
126 push {
r0, r1} /* 保存r0,r1 */
127
128 cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
129
130 push {
lr} /* 保存SVC模式的lr寄存器 */
131 ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
132 blx r2 /* 运行C语言中断处理函数带一个参数,保存在R0寄存器中 */
133
134 pop {
lr} /* 执行完C语言中断服务函数,lr出栈 */
135 cps #0x12 /* 进入IRQ模式 */
136 pop {
r0, r1}
137 str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
138
139 pop {
r0}
140 msr spsr_cxsf, r0 /* 恢复spsr */
141
142 pop {
r0-r3, r12} /* r0-r3,r12出栈 */
143 pop {
lr} /* lr出栈 */
144 subs pc, lr, #4 /* 将lr-4赋给pc */
145
146
147
148 /* FIQ中断 */
149 FIQ_Handler:
150
151 ldr r0, =FIQ_Handler
152 bx r0
153
第10行到第17行是中断向量表,当指定的中断发生以后就会调用对应的中断复位函数,比如复位中断发生以后就会执行第10行代码,也就是调用函数Reset_Handler,函数Reset_Handler就是复位中断的中断服务函数,其它的中断同理。
第20到153行就是对应的中断服务函数,中断服务函数都是用汇编编写的,我们只编写了复位中断服务函数Reset_Handler和IRQ中断服务函数IRQ_Handler,其它的中断暂时没有用到,所以写成了死循环。
1.2 GIC控制器
iMX6ULL(Cortex-A)的中断控制器叫做GIC,我们可以在“i.MX6UL终结者光盘资料\09_其他参考资料”中查看参考手册《ARM Generic Interrupt Controller(ARM GIC控制器》。
看过SOC架构的同学知道,CPU接受外部的中断处理请求,并进行处理,其实是一个被动接受的过程,这样好处是既能保证主任务的执行效率,又能及时获知外部的请求,从而处理重要的设备请求操作。
GIC的全称为general interrupt controller,主要作用可以归结为:接受硬件中断信号,并进行简单处理,通过一定的设置策略,分给对应的CPU进行处理。GIC有4个版本:V1~V4,V1是最老的版本,已经不再使用。GIC V2是给 ARMv7-A 架构使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3和V4是给 ARMv8-A/R 架构使用的。
我们先来看看GIC的大致结构组成(GICV2总体框图)。
这是经典的GIC V2图,实际上大家能看到的经典的图也是这个。左侧是中断源,中间就是GIC控制器,最右边是控制器向内核发送中断信息。
这里面把中断源分为了3类:
SPI:这是shared peripheral interrupt , 这是常见的外部设备中断,也定义为共享中断,比如按键触发一个中断,手机触摸屏触发的中断,共享的意思是说可以多个Cpu或者说Core处理,不限定特定的Cpu。一般定义的硬件中断号范围31~1019。
PPI:这里指的是private peripheral interrupt,中断号范围16~31,私有中断,为什么这样说呢,这些中断一般是发送给特定的Cpu的,比如每个Cpu有自己对应的Physicaltimer,产生的中断信号就发送给这个特定的cpu进行处理。
SGI:这个中断有些同学遇到的比较少,software generatedinterrupt,软件触发产生的中断,中断号范围0~15,也就是最前的16个中断。如果在X86平台上做过开发工作的同学可能有印象,其实这就是相当于IPI,简单的说Cpu_1要给Cpu_2发送特定信息,比如时间同步,全局进程调度信息,就通过软件中断方式,目标Cpu接受到这样的中断信息,可以获取到信息是哪个Cpu发送过来的,具体的中断ID是哪个数字,从而找到对应处理方式进行处理。
iMX6U LL总共使用了128个设备中断ID,加上前面属于PPI和SGI的32个ID,iMX6ULL 的中断源共有 128+32=160个,这128个中断ID对应的中断在《I.MX6ULL参考手册.pdf》的“3.2 Cortex A7 interrupts”小节,中断源表如图 1.2.2所示:
限于篇幅原因,上图中并没有给出I.MX6ULL完整的中断源,完整的中断源大家可以自行查阅《I.MX6ULL参考手册.pdf》的3.2小节。接下来我们打开“i.MX6UL终结者光盘资料\04_裸机例程源码\”裸机例程“8_int”,我们前面移植了NXP官方SDK中的文件MCIMX6Y2C.h,在此文件中定义了一个枚举类型 IRQn_Type,此枚举类型就枚举出了iMX6ULL 的所有中断,使用命令“vi core/MCIMX6Y2.h”,打开该文件即可查看。如图 1.2.3所示:
从第27行开始,枚举类型IRQn就枚举出了iMX6ULL的所有中断就是中断号,代码如下所示:
24 /** Interrupt Number Definitions */
25 #define NUMBER_OF_INT_VECTORS 160 /*中断源160个 */
26
27 typedef enum IRQn {
28 /* Auxiliary constants */
29 NotAvail_IRQn = -128,
30
31 /* Core interrupts */
32 Software0_IRQn = 0,
33 Software1_IRQn = 1,
34 Software2_IRQn = 2,
35 Software3_IRQn = 3,
36 Software4_IRQn = 4,
37 Software5_IRQn = 5,
38 Software6_IRQn = 6,
39 Software7_IRQn = 7,
40 Software8_IRQn = 8,
41 Software9_IRQn = 9,
42 Software10_IRQn = 10,
43 Software11_IRQn = 11,
44 Software12_IRQn = 12,
45 Software13_IRQn = 13,
46 Software14_IRQn = 14,
47 Software15_IRQn = 15,
48 VirtualMaintenance_IRQn = 25,
49 HypervisorTimer_IRQn = 26,
50 VirtualTimer_IRQn = 27,
51 LegacyFastInt_IRQn = 28,
52 SecurePhyTimer_IRQn = 29,
53 NonSecurePhyTimer_IRQn = 30,
54 LegacyIRQ_IRQn = 31,
55
56 /* Device specific interrupts */
57 IOMUXC_IRQn = 32,
58 DAP_IRQn = 33,
59 SDMA_IRQn = 34,
60 TSC_IRQn = 35,
61 SNVS_IRQn = 36,
62 LCDIF_IRQn = 37,
63 RNGB_IRQn = 38,
64 CSI_IRQn = 39,
65 PXP_IRQ0_IRQn = 40,
66 SCTR_IRQ0_IRQn = 41,
67 SCTR_IRQ1_IRQn = 42,
68 WDOG3_IRQn = 43,
69 Reserved44_IRQn = 44,
70 APBH_IRQn = 45,
71 WEIM_IRQn = 46,
72 RAWNAND_BCH_IRQn = 47,
73 RAWNAND_GPMI_IRQn = 48,
74 UART6_IRQn = 49,
75 PXP_IRQ1_IRQn = 50,
76 SNVS_Consolidated_IRQn = 51,
77 SNVS_Security_IRQn = 52,
78 CSU_IRQn = 53,
79 USDHC1_IRQn = 54,
80 USDHC2_IRQn = 55,
81 SAI3_RX_IRQn = 56,
82 SAI3_TX_IRQn = 57,
83 UART1_IRQn = 58,
84 UART2_IRQn = 59,
85 UART3_IRQn = 60,
86 UART4_IRQn = 61,
87 UART5_IRQn = 62,
88 eCSPI1_IRQn = 63,
89 eCSPI2_IRQn = 64,
90 eCSPI3_IRQn =65,
91 eCSPI4_IRQn = 66,
92 I2C4_IRQn = 67, .
93 I2C1_IRQn = 68,
94 I2C2_IRQn = 69,
95 I2C3_IRQn = 70,
96 UART7_IRQn = 71,
97 UART8_IRQn = 72,
98 Reserved73_IRQn = 73,
99 USB_OTG2_IRQn = 74,
100 USB_OTG1_IRQn = 75,
101 USB_PHY1_IRQn = 76,
102 USB_PHY2_IRQn = 77,
103 DCP_IRQ_IRQn = 78,
104 DCP_VMI_IRQ_IRQn = 79,
105 DCP_SEC_IRQ_IRQn = 80,
106 TEMPMON_IRQn = 81,
107 ASRC_IRQn = 82,
108 ESAI_IRQn =83,
109 SPDIF_IRQn = 84,
110 Reserved85_IRQn = 85,
111 PMU_IRQ1_IRQn = 86,
112 GPT1_IRQn = 87,
113 EPIT1_IRQn = 88,
114 EPIT2_IRQn = 89,
115 GPIO1_INT7_IRQn = 90,
116 GPIO1_INT6_IRQn = 91,
117 GPIO1_INT5_IRQn = 92,
118 GPIO1_INT4_IRQn = 93,
119 GPIO1_INT3_IRQn = 94,
120 GPIO1_INT2_IRQn = 95, .
121 GPIO1_INT1_IRQn = 96,
122 GPIO1_INT0_IRQn = 97,
123 GPIO1_Combined_0_15_IRQn = 98,
124 GPIO1_Combined_16_31_IRQn = 99,
125 GPIO2_Combined_0_15_IRQn = 100,
126 GPIO2_Combined_16_31_IRQn = 101,
127 GPIO3_Combined_0_15_IRQn = 102,
128 GPIO3_Combined_16_31_IRQn = 103,
129 GPIO4_Combined_0_15_IRQn = 104,
130 GPIO4_Combined_16_31_IRQn = 105,
131 GPIO5_Combined_0_15_IRQn = 106,
132 GPIO5_Combined_16_31_IRQn = 107,
133 Reserved108_IRQn = 108,
134 Reserved109_IRQn = 109,
135 Reserved110_IRQn = 110,
136 Reserved111_IRQn = 111,
137 WDOG1_IRQn = 112,
138 WDOG2_IRQn = 113,
139 KPP_IRQn = 114,
140 PWM1_IRQn = 115,
141 PWM2_IRQn = 116,
142 PWM3_IRQn = 117,
143 PWM4_IRQn = 118,
144 CCM_IRQ1_IRQn = 119,
145 CCM_IRQ2_IRQn = 120,
146 GPC_IRQn = 121,
147 Reserved122_IRQn = 122,
148 SRC_IRQn = 123,
149 Reserved124_IRQn = 124,
150 Reserved125_IRQn = 125,
151 CPU_PerformanceUnit_IRQn = 126,
152 CPU_CTI_Trigger_IRQn = 127,
153 SRC_Combined_IRQn = 128,
154 SAI1_IRQn = 129,
155 SAI2_IRQn = 130,
156 Reserved131_IRQn = 131,
157 ADC1_IRQn = 132,
158 ADC_5HC_IRQn = 133,
159 Reserved134_IRQn = 134,
160 Reserved135_IRQn = 135,
161 SJC_IRQn = 136,
162 CAAM_Job_Ring0_IRQn = 137,
163 CAAM_Job_Ring1_IRQn = 138,
164 QSPI_IRQn = 139,
165 TZASC_IRQn = 140,
166 GPT2_IRQn = 141,
167 CAN1_IRQn = 142,
168 CAN2_IRQn = 143,
169 Reserved144_IRQn = 144,
170 Reserved145_IRQn = 145,
171 PWM5_IRQn = 146,
172 PWM6_IRQn = 147,
173 PWM7_IRQn = 148,
174 PWM8_IRQn = 149,
175 ENET1_IRQn = 150,
176 ENET1_1588_IRQn = 151,
177 ENET2_IRQn = 152,
178 ENET2_1588_IRQn = 153,
179 Reserved154_IRQn = 154,
180 Reserved155_IRQn = 155,
181 Reserved156_IRQn = 156,
182 Reserved157_IRQn = 157,
183 Reserved158_IRQn = 158,
184 PMU_IRQ2_IRQn = 159
185 } IRQn_Type;
186
187 /*!
188 * @}
189 */ /* end of group Interrupt_vector_numbers */
下面我们来看下GIC的架构,GIC架构分为了两个逻辑块: Distributor和CPU Interface,也就是分发器端和 CPU 接口端。这两个逻辑块的含义如下:
Distributor(分发器端):分发器块负责中断优先级划分并分发到对应的CPU Interface。具体内容如下:
1.全局中断使能转发到CPU Interface
2.启用或禁用每一个中断
3.设置每个中断的优先级
4.设置每个中断的目标处理器列表
5.将每个外设中断设置为电平触发或边缘触发
6.将每个中断设置为0组或1组
CPU Interface(CPU接口端):CPU接口端听名字就知道是和CPU Core相连接的,因此在本小姐开始的“GICV2总体框图”中每个CPU Core都可以在GIC中找到一个与之对应的CPU Interface。CPU接口端就是分发器和CPU Core之间的桥梁,CPU接口端主要工作如下:
1.向处理器发送中断请求信号
2.确认中断
3.表示中断处理完成
4.为处理器设置中断优先级掩码
5.定义处理器的抢占策略
6.确定处理器的最高优先级挂起中断
在我们的例程“8_int”中的文件core_ca7.h定义了GIC结构体,此结构体里面的寄存器分为分发器端和CPU接口端,使用命令“vi core/core_ca7.h ”打开该文件即可查看,如图 1.2.2所示:
我们看到寄存器定义如下所示:
530 /*******************************************************************************
531 * GIC相关内容
532 *有关GIC的内容,参考:ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf
533 ******************************************************************************/
534
535 /*
536 * GIC寄存器描述结构体,
537 * GIC分为分发器端和CPU接口端
538 */
539 typedef struct
540 {
541 uint32_t RESERVED0[1024];
542 __IOM uint32_t D_CTLR; /*!< Offset: 0x1000 (R/W) */
543 __IM uint32_t D_TYPER; /*!< Offset: 0x1004 (R/ ) */
544 __IM uint32_t D_IIDR; /*!< Offset: 0x1008 (R/ ) */
545 uint32_t RESERVED1[29];
546 __IOM uint32_t D_IGROUPR[16]; /*!< Offset: 0x1080 - 0x0BC (R/W) */
547 uint32_t RESERVED2[16];
548 __IOM uint32_t D_ISENABLER[16]; /*!< Offset: 0x1100 - 0x13C (R/W) */
549 uint32_t RESERVED3[16];
550 __IOM uint32_t D_ICENABLER[16]; /*!< Offset: 0x1180 - 0x1BC (R/W) */
551 uint32_t RESERVED4[16];
552 __IOM uint32_t D_ISPENDR[16]; /*!< Offset: 0x1200 - 0x23C (R/W) */
553 uint32_t RESERVED5[16];
554 __IOM uint32_t D_ICPENDR[16]; /*!< Offset: 0x1280 - 0x2BC (R/W) */
555 uint32_t RESERVED6[16];
556 __IOM uint32_t D_ISACTIVER[16]; /*!< Offset: 0x1300 - 0x33C (R/W) */
557 uint32_t RESERVED7[16];
558 __IOM uint32_t D_ICACTIVER[16]; /*!< Offset: 0x1380 - 0x3BC (R/W) */
559 uint32_t RESERVED8[16];
560 __IOM uint8_t D_IPRIORITYR[512]; /*!< Offset: 0x1400 - 0x5FC (R/W) */
561 uint32_t RESERVED9[128];
562 __IOM uint8_t D_ITARGETSR[512]; /*!< Offset: 0x1800 - 0x9FC (R/W) */
563 uint32_t RESERVED10[128];
564 __IOM uint32_t D_ICFGR[32]; /*!< Offset: 0x1C00 - 0xC7C (R/W) */
565 uint32_t RESERVED11[32];
566 __IM uint32_t D_PPISR; /*!< Offset: 0x1D00 (R/ ) Private */
567 __IM uint32_t D_SPISR[15]; /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared */
568 uint32_t RESERVED12[112];
569 __OM uint32_t D_SGIR; /*!< Offset: 0x1F00 ( /W) Software */
570 uint32_t RESERVED13[3];
571 __IOM uint8_t D_CPENDSGIR[16]; /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI */
572 __IOM uint8_t D_SPENDSGIR[16]; /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI */
573 uint32_t RESERVED14[40];
574 __IM uint32_t D_PIDR4; /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 */
575 __IM uint32_t D_PIDR5; /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 */
576 __IM uint32_t D_PIDR6; /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 */
577 __IM uint32_t D_PIDR7; /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 */
578 __IM uint32_t D_PIDR0; /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 */
579 __IM uint32_t D_PIDR1; /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 */
580 __IM uint32_t D_PIDR2; /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 */
581 __IM uint32_t D_PIDR3; /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 */
582 __IM uint32_t D_CIDR0; /*!< Offset: 0x1FF0 (R/ ) Component ID0 */
583 __IM uint32_t D_CIDR1; /*!< Offset: 0x1FF4 (R/ ) Component ID1 */
584 __IM uint32_t D_CIDR2; /*!< Offset: 0x1FF8 (R/ ) Component ID2 */
585 __IM uint32_t D_CIDR3; /*!< Offset: 0x1FFC (R/ ) Component ID3 */
586
587 __IOM uint32_t C_CTLR; /*!< Offset: 0x2000 (R/W) CPU Interface */
588 __IOM uint32_t C_PMR; /*!< Offset: 0x2004 (R/W) Interrupt */
589 __IOM uint32_t C_BPR; /*!< Offset: 0x2008 (R/W) Binary Point */
590 __IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt */
591 __OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt*/
592 __IM uint32_t C_RPR; /*!< Offset: 0x2014 (R/ ) Running Priority*/
593 __IM uint32_t C_HPPIR; /*!< Offset: 0x2018 (R/ ) Highest Priority*/
594 __IOM uint32_t C_ABPR; /*!< Offset: 0x201C (R/W) Aliased Binary*/
595 __IM uint32_t C_AIAR; /*!< Offset: 0x2020 (R/ ) Aliased Interrupt*/
596 __OM uint32_t C_AEOIR; /*!< Offset: 0x2024 ( /W) Aliased End Of*/
597 __IM uint32_t C_AHPPIR; /*!< Offset: 0x2028 (R/ ) Aliased Highest*/
598 uint32_t RESERVED15[41];
599 __IOM uint32_t C_APR0; /*!< Offset: 0x20D0 (R/W) Active Priority*/
600 uint32_t RESERVED16[3];
601 __IOM uint32_t C_NSAPR0; /*!< Offset: 0x20E0 (R/W) Non-secure*/
602 uint32_t RESERVED17[6];
603 __IM uint32_t C_IIDR; /*!< Offset: 0x20FC (R/ ) CPU Interface */
604 uint32_t RESERVED18[960];
605 __OM uint32_t C_DIR; /*!< Offset: 0x3000 ( /W) Deactivate */
606 } GIC_Type;
607
结构体GIC_Type就是GIC控制器,列举出GIC控制器的所有寄存器,我们可以通过结构体GIC_Type来访问GIC的所有寄存器。
第542行是GIC的分发器端相关寄存器,其相对于 GIC 基地址偏移为 0X1000,获取到GIC基地址以后只需要加上0X1000即可访问GIC分发器端寄存器。
第587行是GIC的CPU接口端相关寄存器,其相对于 GIC 基地址的偏移为 0X2000,获取到GIC基地址以后只需要加上0X2000即可访问GIC的CPU接口端寄存器。
那么问题来了?GIC控制器的寄存器基地址在哪里呢?这个就需要用到Cortex-A的CP15协处理器了,下面我们就来看一下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”。
这两个文档在光盘资料的:“i.MX6UL终结者光盘资料\10_其它参考资料”目录下。
在基于ARM的嵌入式应用系统中,存储系统通常是通过系统控制协处理器CP15完成的。ARM处理器使用协处理器15(CP15)的寄存器来控制cache、TCM和存储器管理。CP15包含16个32位的寄存器,其编号为0~15。在中断中也会使用到。CP15协处理器的访问通过如下两个指令完成:
MRC: 将CP15协处理器中的寄存器数据读到ARM寄存器中。
MCR: 将ARM寄存器的数据写入到CP15协处理器寄存器中。
MRC就是读CP15寄存器,MCR就是写CP15寄存器,MCR指令格式如下:
MCR {} p15,,,,{,}
<cond>
:为指令执行的条件码。当忽略时指令为无条件执行。
<opc1>
:为协处理器将执行的操作的操作码。对于CP15协处理器来说,<opcode_1>永 远 为0b000,当< opcode_1>不为0b000时,该指令操作结果不可预知。
<Rt>
:作为源寄存器的ARM寄存器,其值将被传送到协处理器寄存器中。
<CRn>
:作为目标寄存器的协处理器寄存器,其编号可能是C0,C1,…,C15。
<CRm>
:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm 设置为C0,否则结果不可预测。
:可选的协处理器特定操作码,当不需要的时候要设置为0。
下面的指令从ARM寄存器R4中中将数据传送到协处理器CP15的寄存器C1中。其中R4为ARM寄存器,存放源操作数,C1、C0为协处理器寄存器,为目标寄存器,opcode_1为0,opcode_2为0。
MCR p15, 0, R4, C1, C0, 0
MRC指令将协处理器的寄存器中的数值传送到ARM处理器的寄存器中、如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。MRC指令格式如下:
MRC {<cond>} p15,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>}
参数用法与MCR指令相同。
CP15协处理器有16个32位寄存器,c0~c15,如图 1.2.3所示:
本章我们主要来看一下c0、c1、c12 和c15这四个寄存器,因为我们本章实验要用到这四个寄存器,其他的寄存器大家参考上面两个文档。
首先我们来看一下c0寄存器,在使用CRn、opc1、CRm和opc2通过不同的搭配,其得到的寄存器含义是不同的。c0在不同的搭配情况下含义如图 1.2.4所示:
在上图中当MRC/MCR指令中的CRn=c0,opc1=0,CRm=c0,opc2=0的时候就表示此时的c0就是MIDR寄存器,也就是主ID寄存器,这个也是c0的基本作用。c0 作为MDIR寄存器的时候其含义如图 16.1.2.5所示:
上图中的寄存器是32位的,各个位的功能如下所示:
31~24(Implementer):厂商编号, 0X41, ARM。
23~20(Variant):内核架构的主版本号,使用rnpn中rn的n来表示。r0p1即为0。
19~16(Indicates the architecture code):架构编号, 0XF, ARMv7 架构。
15~4(Primary part number):内核版本号, 0XC07, Cortex-A7 MPCore内核。
3~0(Revision):内核架构的次版本号,使用rnpn中pn的n来表示。r0p1即为1。
接下来我们看一下c1寄存器,c1寄存器同样通过不同的配置,其代表的含义也不同,如图 1.2.6所示:
从上图可以看出当MRC/MCR指令中的CRn=c1,opc1=0,CRm=c0,opc2=0的时候就表示此时的c1就是SCTLR寄存器,也就是系统控制寄存器,这个是c1的基本作用。SCTLR寄存器主要是完成控制功能的,比如使能或者禁止MMU、 I/D Cache等,c1作为SCTLR寄存器的时候其含义如图 16.1.2.7所示:
SCTLR寄存器的位比较多,我们重点看下本章用到的几个位:
V(bit13):中断向量表基地址选择为。为0表示中断向量基地址是0X00000000,软件可以使用VBAR来重映射此基地址;为1表示中断向量基地址为0XFFFF0000,此基地址不能被重映射。
I(bit12):ICache使能位。为0表示不使能ICache,为1表示使能ICache。
Z(bit11):分支预测启用位。分支预测,也称为程序流预测,在启用MMU时使能此位。
SW(bit10):SWP和SWPB使能位。为0的时候关闭SWP和SWPB指令,当为1的时候就使能SWP和SWPB指令。
C(bit2):D Cache和缓存一致性使能位,为0的时候禁止D Cache和缓存一致性,为1的时候使能。
A(bit1):内存对齐检查使能位,为0的时候关闭内存对齐检查,为1的时候使能内存对齐检查。
M(bit0):MMU使能位,为0的时候禁止MMU,为1的时候使能 MMU。
如果要读写SCTLR的话,就可以使用如下命令:
MRC p15, 0, , c1, c0, 0 ;读取SCTLR寄存器,数据保存到 Rt 中。
MCR p15, 0, , c1, c0, 0 ;将Rt中的数据写到SCTLR(c1)寄存器中。
接下来是c12寄存器,c12寄存器通过不同的配置,其代表的含义也不同,如图 1.2.8所示:
在上面的图中当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
接下来是c15寄存器,该也可以通过不同的配置得到不同的含义,参考文档《Cortex-A7 Technical ReferenceManua.pdf》第68页“4.2.16 c15 registers”,其配置如图 1.2.9所示:
在上面图中,我们需要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] ;读取GIC_IAR的值(CPU接口端起始地址+0XC处的寄存器值)
关于CP15协处理器就讲解到这里,简单总结一下,通过c0寄存器可以获取到处理器内核信息;通过c1寄存器可以使能或禁止MMU、I/D Cache等;通过c12寄存器可以设置中断向量偏移;通过c15寄存器可以获取GIC基地址。关于CP15的其他寄存器,大家可以自行查阅本节前面列举的2份ARM官方资料。
接下来我们看一下中断使能,中断使能包括两部分,一个是IRQ或者FIQ总中断使能,另一个就是ID0~ID1019 这1020个中断源的使能。
首先我们来看一下IRQ和FIQ总中断使能,IRQ和FIQ分别是外部中断和快速中断的总开关,类似于总开关,然后ID0~ID1019这1020个中断源就类似于对应分支的小开关开关。要想使用这1020个中断源,我们必须先要打开IRQ和FIQ这个总的中断源。因此要想使用iMX6U上的外设中断就必须先打开IRQ总开关。在前面章节的“程序状态寄存器”小节已经讲过了,寄存器CPSR的I=1禁止IRQ,当I=0使能IRQ;F=1禁止FIQ,F=0使能FIQ。我们还有更简单的指令来完成IRQ或者FIQ的使能和禁止,如下所示:
cpsid i ;禁止 IRQ 中断。
cpsie i ;使能 IRQ 中断。
cpsid f ;禁止 FIQ 中断。
cpsie f ;使能 FIQ 中断。
在上面我们使能了总中断,接下来我们看看ID0-ID1019中断的使能。GIC里寄存器GICD_ISENABLERn和GICD_ ICENABLERn用来完成外部中断的使能和禁止,对于Cortex-A7内核来说中断ID使用了512个。一个位控制一个中断ID的使能,所以需要 512/32=16 个GICD_ISENABLER寄存器来完成中断的使能。同理,也需要16个GICD_ICENABLER寄存器来完成中断的禁止。其中GICD_ISENABLER0的150位对应ID150 的SGI中断, GICD_ISENABLER0的3116对应ID3116的PPI中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15就是控制 SPI 中断的。
学过单片机的同学可能知道在单片机里面也会有多个中断源,当这些中断源同时发生的时候单片机是按照中断源的优先级来处理的,而且中断的优先级是可以配置的。Cortex-A7的中断优先级分为抢占优先级和子优先级,最多可以支持256个优先级,数字越小,优先级越高,半导体厂商自行决定选择多少个优先级。iMX6ULL支持32个优先级。在使用中断的时候需要初始化GICC_PMR寄存器,该寄存器决定使用几级优先级,它的寄存器结构如图 1.2.10所示:
GICC_PMR寄存器只有低8位有效,这8位最多可以设置256个优先级,其它优先级数设置如下表所示:
Bit7:bit0 优先级数
11111111 256个优先级
11111110 128个优先级
11111100 64个优先级
11111000 32个优先级
11110000 16个优先级
I.MX6ULL支持32个优先级,所以GICC_PMR要设置为0b11111000。
接下来我们看一下抢占优先级与子优先级的设置。抢占优先级和子优先级各占多少位是由寄存器GICC_BPR来决定的,GICC_BPR寄存器结构如下图:
从上图可以看到寄存器GICC_BPR只有低3位有效,其值不同,抢占优先级和子优先级占用的位数也不同,如下图:
在上图中最后一列的“Field with binary point”中的g表示的是抢占优先级,s是子优先级,分别用个数代表是几级。比如GICC_BPR的Binary Point设置值为0,其对应的Field with binary point列的值是“ggggggg_s”,这个值里面有7个g,1个s,它表示:7级抢占优先级,1级子优先级。
为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6ULL 的优先级位数为 5(32个优先级),所以可以设置Binary point为2,表示5个优先级位全部为抢占优先级。
接下来我们看一下优先级的设置。前面已经设置好了iMX6ULL 一共有32个抢占优先级,数字越小优先级越高。使用某个中断的时候就可以设置其优先级为 0~31。某个中断ID中断优先级设置由寄存器D_IPRIORITYR来完成,一共有512个D_IPRIORITYR寄存器(Cortex-A7使用了512个中断号,每个中断号配有一个优先级寄存器)。如果优先级个数为32的话,使用寄存器D_IPRIORITYR 的bit7~4位来设置优先级,也就是说实际的优先级要左移 3 位。例如要设置ID40中断的优先级为5,示例代码如下:
GICD_IPRIORITYR[40] = 5 << 3
;
有关优先级设置的内容就介绍到这里,优先级设置主要有下面三部分:
1.设置寄存器GICC_PMR,配置优先级个数,比如iMX6ULL支持32级优先级
2.设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
3.设置指定中断ID的优先级,也就是设置外设的优先级。
2 原理分析
可以参考第十一章,按键输入实验,只不过本实验按键采用的是中断的方式。