/********************************************/
时间 :2020/03/29/
作者 :YOKI
导师 :正点原子 左忠凯
硬件 :正点原子 ALPHA I.MX LINUX 开发板
/*******************************************/
写在前面的话:
中断函数涉及到 ARM Cortex-A7内核 构架,建议学习本视频前回到 第6章 去 复习 一下构架的有关知识!
建议学习本视频前回到 第6章 去复习一下构架的有关知识!
回到 第6章 去复习 一下构架的有关知识!
重要的事情说三遍! 不然只有两个字:懵比!!!!
重要知识点:
ARM Cortex-A7 构架 提供了 16个 32位 通用寄存器( R0-R15)和2个 程序状态寄存器
IRQ(Interrupt request) 模式下各寄存器的含义:
R0-R12 通用寄存器
R13 SP_irq SP指针(IRQ模式下)
R14 LR_irq 连接寄存器 (IRQ模式下) 存放 子函数的返回地址
R15 PC 程序计数器 将 LR_irq 的值 传递给 PC 可实现程序跳转
CPSR 当前程序状态寄存器
SPSR_irq 备份程序状态寄存器
CP15协处理器 指令
MRC : 将 CP15协处理器 的寄存器数据 写到 ARM 寄存器
MCR : 将 ARM 处理器 的寄存器数据 写到 CP15 寄存器
MRC : 读 CP15 寄存器 MCR 写 CP15 寄存器
使用格式:
MCR p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc12>
mrc p15, 4, r1, c15, c0, 0
@ 从CP15的 C0寄存器 内的值 写到 R1寄存器 中
@ Cortex-A7 Technical ReferenceManua.pdf P68 P138
@ 含义 读取 CBAR 寄存器(CBAR 寄存器保存了 GIC 寄存器的首地址)
@ GIC寄存器(通用中断控制寄存器)参考:《Cortex-A7 Technical ReferenceManua.pdf》 P178
IRQ中断服务的编写:
学习过 51 或者 STM32 的人 都知道 中断函数 是在 当前程序运行过程中,由中断事件(Event)触发后进入一段临时运行的程序,
这段程序执行完成后要回到中断发生前的程序运行状态继续执行原来的程序。这就要求,从当前程序进入到中断服务函数后首先要进行
的一件事是 保护现场 :保存当前程序运行的状态或数据 即保存CPU中相关寄存器的数据 IMX6ULL 采用 Cortex-A7 构架
Cortex-A7 构架 有 9 种运行模式,本节只以 IRQ(Interrupt request) 模式 进行讲解
参考:正点原子I.MX6U Linux 嵌入式驱动开发指南 第6章 Cortex-A7 MPCore构架 / 第17章 GPIO中断实验
IRQ(Interrupt request) 模式下各寄存器的含义:
R0-R12 通用寄存器
R13 SP_irq SP指针(IRQ模式下)
R14 LR_irq 连接寄存器 (IRQ模式下) 存放 子函数的返回地址
R15 PC 程序计数器 将 LR_irq 的值 传递给 PC 可实现程序跳转
CPSR 当前程序状态寄存器
SPSR_irq 备份程序状态寄存器
IRQ_Interrupt_Handler:
push {lr} @ 保存 lr 地址 压栈 (存放IRQ_Interrupt_Handler 子程序返回地址) 压栈(栈号:0)
push {r0-r3,r2} @ 保存 r0-r3, r12 寄存器 (其实是为了腾出R0-R3,R12的空间给后面使用) 压栈(栈号:1)
@ 保护 当前现场
mrs r0,spsr @ 将 spsr寄存器数据存入寄存器r0
push {r0} @ 保存 r0 即 保存 spsr 寄存器 (保存 备份程序状态寄存器) 压栈(栈号:2)
@ 保护 当前现场
mrc p15, 4, r1, c15, c0, 0 @ 从CP15的C0寄存器内的值到R1寄存器中 (读取 CBAR 寄存器)
@ 含义 读取 CBAR 寄存器(CBAR 寄存器保存了 GIC 寄存器的首地址)
@ 获取了 GIC寄存器(通用中断控制寄存器)的基地址 就可以设置GIC相关的寄存器了
@ R1 中当前存放着 GIC 寄存器的首地址
参考:《Cortex-A7 Technical ReferenceManua.pdf》:4.2.16 P68 P138
/* GIC 内部存储器映射表 */
0X0000——0X0FFF Reserved 保留位
0X1000——0X1FFF Distributor 分发器(分配器)
0X2000——0X3FFF CPU interface CPU接口
0X4000——0X4FFF Virtual interface control/common base address 虚拟接口控制/公共基地址
0X5000——0X5FFF Virtual interface control/processsor-specific base address 虚拟接口控制/专用基地址
0X5000——0X5FFF Virtual CPU interface 虚拟CPU接口控制
参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.2.1 P178
add r1, r1, #0X2000 @ GIC基地址加0X2000,也就是GIC的CPU接口端基地址
ldr r0, [r1, #0XC] @ GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
@ GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
@ 这个中断号来决定调用哪个中断服务函数
@ 将 GICC_IAR寄存器 的值 存入 R0
/* CPU interface CPU接口 概要表 */
地址 寄存器名称 权限 复位值 简介
0X0000 GICC_CTLR 读/写 复位值:0X00000000 CPU接口控制寄存器
0X0004 GICC_PMRn 读/写 复位值:0X00000000 中断优先级标志寄存器
0X0008 GICC_BPR 读/写 复位值:0X00000002
0X00000003 二进制点位寄存器?
0X000C GICC_IAR 只读 复位值:0X000003FF 中断确认寄存器
参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.3.3 P188
/* GICC_IAR中断确认寄存器说明 */
GICC_IAR (32bit)
bit[31-13]:保留位 bit[10-12]:CPU ID bit[9-0]:Interrupt ID
也就是说 GICC_IAR寄存器的 bit0-bit9 用来保存中断号
参考:《ARM Generic Interrupt controller (ARM GIC控制器)V2.0.pdf》:4.4.4 P135
push {r0, r1} @ 压栈 保存r0,r1 内容(腾出 R0 R1 内存空间) 压栈(栈号:3)
@ R0 保存着 GICC_IAR寄存器 的值
@ R1 保存着 GIC的CPU接口端基地址
cps #0x13 @ 对 cpsr 寄存器 写入 0X13 进入SVC模式,允许其他中断再次进去
push {lr} @ 保存SVC模式的lr寄存器 压栈(栈号:4)
ldr r2, =system_irqhandler @ 加载C语言中断处理函数到r2寄存器中
blx r2 @ 调用 C语言中断处理函数system_irqhandler 带有一个参数,保存在R0寄存器中
@ C语言编写的 system_irqhandler(unsigned int giccIar) 是具体的中断处理函数
@ 这个函数的参数 giccIar: GICC_IAR寄存器 的值
pop {lr} @ 执行完C语言中断服务函数,lr出栈 出栈(栈号:4)
cps #0x12 @ 对 cpsr 寄存器 写入 0X12 进入IRQ模式
pop {r0, r1} @ R0 保存着 GICC_IAR寄存器 的值 出栈(栈号:3)
@ R1 保存着 GIC的CPU接口端基地址
str r0, [r1, #0X10] @ 将 R1 的地址加上一个偏移量 0X10 (GICC_EOIR 地址)
@ 把 R0 的内容(GICC_IAR寄存器的值) 写入 R1 (GICC_EOIR 地址)
/* CPU interface CPU接口 概要表 */
0X0010 GICC_EOIR 只写 复位值:无 中断结束寄存器
参考:《Cortex-A7 Technical ReferenceManua.pdf》:8.3.3 P189
/* GICC_E0I 中断结束寄存器 说明 */
GICC_E0I (32bit)
bit[31-13]:保留位 bit[10-12]:CPU ID bit[9-0]:EOIINT ID
GICC_E0I寄存器的 bit0-bit9 和 GICC_IAR寄存器的 bit0-bit9 对应
即 当中断程序执行完后要把 GICC_IAR寄存器的值(重点是 Interrupt ID )写入 GICC_E0I寄存器 标志中断结束
参考:《ARM Generic Interrupt controller (ARM GIC控制器)V2.0.pdf》:4.4.5 P138
pop {r0} @ 出栈(栈号:2) 出栈(栈号:2)
@ (栈号:2) r0 保存着中断执行前spsr寄存器 (备份程序状态寄存器)的值
msr spsr_cxsf, r0 @ 将 r0 的值写入 spsr寄存器 (恢复现场)
pop {r0-r3, r12} @ 出栈(栈号:1) 恢复中断执行前 r0-r3,r12 的值 (恢复现场) 出栈(栈号:1)
pop {lr} @ 出栈(栈号:0) 恢复中断执行前 lr 地址 出栈(栈号:0)
subs pc, lr, #4 @ 将lr-4赋给pc
/*为什么是lr-4 而不是lr直接返回?*/
/*ARM 构架下为了提高运行效率存在:取指——译指——执行(三级流水线)
即:
0X1004: xxxxx 当前执行指令
0X1008: xxxxx 提前翻译下1条指令
0X100C: xxxxx 提前获取下2条指令 <---PC
也就是说当系统执行 0X1004地址中存放的指令时,程序计数器 PC 已经指向了 0X100C 地址了,
为了保证中间 0X1008 地址 不被漏掉 需要将 PC指针减一个字节。
/**********************************************************************************************/
个人总结: 中断系统 精讲注释
中断系统的部分确实难啃,视频至少得看2-3遍,目前刚刚看懂了这部分中断函数 梳理一下IRQ_Interrupt_Handler的流程
1、保护现场:设获取当前程序指针位置 设置 中断程序执行完成后的 返回地址
2、保护现场:将 Rn寄存器、CPSR(程序状态寄存器)、SPSR_irq(程序状态备份寄存器)的值 先通过压栈保存起来
压栈时最好注释一下存储的内容和栈号,方便后面恢复现场时按 相反的顺序 出栈(弹夹原理)
3、获取 GIC(通用中断控制)的基地址 进入 GIC寄存器
4、操作 读取 GICC_IAR寄存器 获取中断号 bit[9-0]:Interrupt ID (压栈保存GICC_IAR 数据和GIC入口地址)
5、操作 根据 中断号 判断需要调用的 中断服务函数(跳转到 C语言编写的中断处理函数中进行)
6、中断执行完成 操作 GICC_E0I 寄存器标志中断结束(写入之前存储 GICC_IAR寄存器 的中断号 bit[9-0]:Interrupt ID)
7、现场恢复:逆序 出栈 恢复 SPSR_irq(程序状态备份寄存器)、CPSR(程序状态寄存器)、Rn寄存器数据 (检查出栈顺序(弹夹原理): 先进后出,后进先出)
8、现场恢复:程序指针恢复为中断程序执行前的位值(注意三级流水线)
再次强调一下:
中断函数涉及到 ARM Cortex-A7内核 构架,建议学习本视频前回到第6章去复习一下构架的有关知识,要不然学起来很吃力。
两个字形容:懵比!
本节我还没编写 中断服务函数 system_irqhandler(unsigned int giccIar) 所以就不放工程文件夹了
以上注释和代码都是在学习 《正点原子 linux 第二期 裸机开发视频 P33 第15.4讲 IRQ中断函数编写 》 过程中
跟着 左萌主 学习并加入了一点点自己的思考写成的,作为一个小菜鸡,我大着胆子发出来跟大家一起记录学习
如果有帮助请大家自行参考、下载,如果 转载 请注明出处,并在论坛和我联系。如果有错误请大神指正!左萌主赛高!
作者 :YOKI
导师 :正点原子 左忠凯 (请允许我叫你一声导师吧)
硬件 :正点原子 ALPHA I.MX LINUX 开发板
/*********************************************************************************************/
|