I.MX6U学习笔记裸机篇
看了看廷廷的笔记,我感觉我的学习方法还是有点差劲,不知道效率怎么样,但是质量肯定是差
与其多次重复学习洗脑,不如教别人来的快
文件夹软连接
ls -s src dsn
Makefile
注意缩进为tab
赋值符
赋值符
=
:会默认用最晚改(定义)的那个变量:=
:会默认用最开始定义的变量?=
:是一个很有用的赋值符,比如下面这行代码:curname ?= zuozhongkai
如果变量 curname 前面没有被赋值,那么此变量就是“zuozhongkai”,如果前面已经赋过值了,那么就使用前面赋的值。
+=
:变量追加
自动化变量
伪目标
伪目标不代表真正的目标名,在执行 make 命令通过指定伪目标,去执行自己想执行的命令。
如果说文件夹里也有同样的文件比如clean,因为clean没有依赖文件,make clean会认为clean是最新的,而Makefile里的命令执行不了
为了避免这个问题,我们可以将 clean 声明为伪目标,声明方式如下:
.PHONY : clean
I.MX6U 架构
这家伙与我们平时用的stm32也没有说有啥大的区别吧,学完这家伙之后可能转rsicv需要点时间,但是想把他学扎实点
这家伙用的是cortex-A系列的内核,A7,听说四个A7能有865一样性能
可以阅读ARM的手册
没有实际应用过这几种模式,不好评,只知道每一种模式都有一组寄存器供异常处理程序使用,恢复
A7寄存器组
16 个 32 位的通用寄存器(R0~R15)供软件使用,前 15 个(R0~R14)可以用作通用的数据存储,R15 是程序计数器 PC,用来保存将要执行的指令。
ARM 还提供了一个当前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是 CPSR 寄存器的 备份
通用寄存器
R0~R15 就是通用寄存器,通用寄存器可以分为以下三类
- 未备份寄存器
- 备份寄存器
- 程序计数器PC
未备份寄存器也就是R0-R7,几个模式都共用同一套物理寄存器,如果切换模式,可能会被重新写掉,所以没有特殊用途
备份寄存器R8-R12有两种物理寄存器,一种是给FIQ快速请求中断的,另一种是通用的(看上图
FIQ 模式下中断处理程序可以使用 R8~R12 寄存器,因为 FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。
虽然说我不明白fiq后为什么不用保存现场啊,我猜是因为用了另外一套,所以原有的备份寄存器可以不被中断所用,中断返回后还是原有的数据
到了R13的SP栈指针寄存器
(还是备份寄存器),有8个,1个是通用的,另外7个是7个模式下的。
R14LR链接寄存器
有7个寄存器,一个是用户模式(User)、系统模式(Sys)和超级监视模式(Hyp)所共有的,剩下的 6 个分别对应 6 种不同的模式。
LR链接寄存器
的用途:
R15程序计数器:学过计算机组成原理的应该了解过流水线,因为流水线的缘故,PC会比现在执行的地方加多8个字节
ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执行, 比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放 在 R15(PC)中。我们喜欢以当前正在执行的指令作为参考点,也就是以第一条指令为参考点, 那么 R15(PC)中存放的就是第三条指令,换句话说就是 R15(PC)总是指向当前正在执行的指令地址再加上 2 条指令的地址。对于 32 位的 ARM 处理器,每条指令是 4 个字节,所以:
R15 (PC)值 = 当前执行的程序位置 + 8 个字节。
程序状态寄存器
CPSR只有一个物理寄存器,所以容易被爆金币,故有SPSR备份程序状态寄存器
User和Sys共用一套备份状态寄存器,其余6个模式每个一个备份寄存器
前半部分与ALU计算溢出有关(可能是ALU吧,反正就是溢出相关),所以只写一部分的
I(bit7):I=1 禁止 IRQ,I=0 使能 IRQ。
F(bit6):F=1 禁止 FIQ,F=0 使能 FIQ。
T(bit5):控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一 起表明指令类型,参考 J(bit24)位。
以上的,可能干看没啥感觉,因为stm32貌似就两种状态,而且也没有自己写操作系统
所以,大概看一下就好了,然后记得有这些标志位,可能不知道什么时候(比如说自己写rtos的时候切换模式?debug的时候打印寄存器?idk
IO初始化
- 使能时钟
- 设置IO复用
- 调整IO电气属性
随便抽取一位幸运IO来看看寄存器
可以看到还是有很多的,注意PAD和MUX的区别
我们主要聚焦于可能用的上的电气属性
PUS(bit15-14)
:push,设置上拉电阻
ODE(bit11)
:对应图 8.1.4.2 中的 ODE,当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。
SPEED(bit7:6)
:对应图 8.1.4.2 中的 SPEED,当 IO 用作输出的时候,此位用来设置 IO 速度,设置如表 8.1.4.2 所示:
DSE(bit5:3)
:对应图 8.1.4.2 中的 DSE,当 IO 用作输出的时候用来设置 IO 的驱动能力,总共有 8 个可选选项,如表 8.1.4.3 所示:
SRE(bit0)
:对应图 8.1.4.2 中的 SRE,设置压摆率,当此位为 0 的时候是低压摆率,当为 1 的时候是高压摆率。这里的压摆率就是 IO 电平跳变所需要的时间.如果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的 IO 做高速通信的话就可以使用高压摆率。
通过上面的介绍,可以看出寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 是用来配置 GPIO1_IO00 的,包括速度设置、驱动能力设置、压摆率设置等等。
当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR
先看DR,此寄存器是 32 位的,一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应 一个 GPIO。当 GPIO 被配置为输出功能以后,向指定的位写入数据那么相应的 IO 就会输出相应的高低电平,比如要设置GPIO1_IO00 输出高电平,那么就应该设置 GPIO1.DR=1。当GPIO被配置为输入模式以后,此寄存器就保存着对应 IO 的电平值,每个位对对应一个 GPIO,例如, 当 GPIO1_IO00 这个引脚接地的话,那么 GPIO1.DR 的 bit0 就是 0。
GIDR
:IO输入输出PSR
:pad status GPIO状态ICR1/2
:中断控制寄存器ICR1 用于配置低 16 个 GPIO, ICR2 用于配置高 16 个 GPIO
IMR
:中断屏蔽寄存器ISR
:中断状态寄存器
Chapter 18: Clock Controller Module(CCM)
CCM里面的外设时钟使能寄存器 。 CMM 有 CM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关
每个CG(2bits)对应着一个一个外设,比如UART2等等,如果想开直接11, CCM_CCGR0=3<<31(可能写错了,需要个|?)
上机
注意区分存储地址
和运行地址
,存储地址是我们放img的地方,运行地址是entry addr,如果要启动,需要bios/启动程序将img搬到起始地址在启动
代码可以存储到 SD 卡、EMMC 或者 NAND 中,但是启动区域不一样
在 I.MX6U 的内部 128KB RAM 中 (0X900000~0X91FFFF),也可以在外部的 DDR 中(0X87800000,起始地址都为 0X80000000,只不过 512MB 的终止地址为 0X9FFFFFFF,而 256MB 容量的终止地址为 0X8FFFFFFF。之所以选择 0X87800000 这个地址是因为后面要讲的 Uboot 其链接地址就是 0X87800000,这样我们统一使用 0X87800000 这个链接地址,不容易记混。
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
-Ttext指定链接地址
还需要将elf转成bin,需要arm-linux-gnueabihf-objcopy
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
-O制定输出格式 binary,-S表示不要复制源文件的重定位信息和符号信息
TODO:什么是重定位信息?
arm-linux-gnueabihf-objdump 反汇编
arm-linux-gnueabihf-objdump -D led.elf > led.dis
将elf反汇编
启动方式
I.MX6U 有一个 BOOT_MODE1 引脚和BOOT_MODE0 引脚
FUSE是熔断,一般不用吧
01串行下载是从usb/串口下载
内部Boot模式
选好内部BOOT之后可以用GPIO控制BOOT rom选哪个来启动了,
镜像烧写
imxdownload干了什么?idk
.imx组成:
- Image vector table:一系列地址向量
- Boot data :启动数据,包括镜像要拷去哪,大小
- Device configuration data:设备配置信息,DDR3配置等等
- 用户可执行文件bin
至今我们不知道为什么不从0x80000000开始
前面塞了一坨屎3KByte, load.imx 在 DDR 中的起始地址就是 0X87800000- 3072=0X877FF400
里面有个self嗷,应该就是ivt了
第一个header:
0X41
DCD数据
复位外设寄存器的默认值。DCD 区域不能超过 1768Byte,DCD 区域结构如图
原文放送
链接脚本
定义了SP栈指针,指向main之后,感觉还缺点啥?
C语言的内存模型是不是少了一些东西?
段,sections,text,data,bss,rodata
SECTIONS{ //段名
. = 0X87800000; //定位计数器赋值
.text : //段名
{
start.o
main.o
*(.text) //*为通配符
}
.rodata ALIGN(4) : {*(.rodata*)} //字节对齐
.data ALIGN(4) : { *(.data) } //字节对齐
__bss_start = .; //bss符号赋值,bss是定义了但没初始化的变量
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
移植官方SDK
设置IO复用
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t inputOnfield)
{
*((volatile uint32_t *)muxRegister) =
IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);
if (inputRegister)
{
*((volatile uint32_t *)inputRegister) = IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
}
}
因为入参比较多,而且宏定义写好了,所以再提一下
- muxRegister:IO复用寄存器地址
- muxMode:IO复用值,就是ALT0-8,设置不同的复用功能
- inputRegister:外设输入IO选择寄存器地址,有些 IO 在设置为其他的复用功能以后还需要置 IO 输 入 寄 存 器 , 比如 GPIO1_IO03 要复用为 UART1_RX的话还需要设置寄存器UART1_RX_DATA_SELECT_INPUT,此寄存器地址为 0X020E0624
- inputDaisy:inputRegister的值
- configRegister:未使用,函数 IOMUXC_SetPinConfig 会使用这个寄存器
- inputOnfield:1/0
这次的Makefile有点长哦,我第一次以为只要用自动变量加上start.S就能解决来着
但是他们稍微升级了一下
CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME ?= ledc
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
OBJS := start.o main.o
$(NAME).bin:$(OBJS)
$(LD) -Timx6ul.lds -o $(NAME).elf $^
$(OBJCOPY) -O binary -S $(NAME).elf $@
$(OBJDUMP) -D -m arm $(NAME).elf > $(NAME).dis
%.o:%.s
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis
升级工程模板
INCLUDE := -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
-I 是因为Makefile语法要求头文件目录需要加入
后面的做法是使用patsubst/
# patsubst函数用于将INCDIRS中的每个目录名前添加-I参数,
# 用法: $(patsubst pattern,replacement,text)
# pattern是%(表示匹配任意字符)
# replacement是-I %(表示在每个匹配到的目录名前添加-I参数)
# text是$(INCDIRS)(表示要对INCDIRS中的每个目录名进行替换)
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
用wildcard将所有包含的目录整合成列表
# 对于每个目录,使用wildcard函数查找该目录下所有的.S文件,并将结果保存到一个列表中
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
去除路径
# 去除SFILES的路径,留下文件名
SFILENDIR := $(notdir $(SFILES))
主频与时钟
默认工作频率为528MHz,但是我们之前开的都是396MHz
时钟源有两部分:32.768KHz(供RTC时钟) 和 24MHz(内核与外设时钟) 的晶振
7路PLL时钟源
ARM PLL
:给ARM内核用的,可编程超到1.3GHz528_PLL
:也叫System_PLL,为固定的22倍频,不可编程(24MHz*22=528MHz)所以这家伙叫528_PLL,同时分出来4路PFD。此 PLL 分出了 4 路 PFD,分别为:PLL2_PFD0~PLL2_PFD3,这 4 路 PFD 和 528_PLL 共同作为其它很多外设的根时钟源。通常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的时钟源,比如内处理逻辑单元、DDR 接口、NAND/NOR 接口等等
USB1_PLL(PLL3)
:USBPHY使用,同时也有4路PFD,固定20倍频=480MHzUSB2_PLL(PLL7)
:同理ENET_PLL(PLL6)
:PLL为20+5/6倍频,500MHz,此PLL用于生成网络的时钟VIDEO_PLL(PLL5)
:用于显示的外设如LCD,PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频AUDIO_PLL(PLL4)
,此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整,PLL 的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选 1/2/4 分频。
内核时钟设置
内核时钟来源于PLL1,而通过CACRR寄存器的ARM_PODF位可对其进行1248分频
498的主频=996/2
528主频=1056/2
696主频=696/1
她最右边还有个分频对吧?假的,没有作用的哦
那怎么设置PLL1主频?再来看一下寄存器 CCM_ANALOG_PLL_ARMn
ENABLE
:为开启时钟输出,0PLL1就shutdown了DIV_SELECT
:pll输出频率,可以设置54-108,输出=FIN*value/2,FIN=24MHz,如果要让pll1输出为1056的话,这个寄存器要设为88
设置这家伙还好,修改PLL1的时候要切去其他时钟源
可以看到2/3都有个CCSR:,这两个是CCSR中的两个位,用于控制时钟源
当我们调整PLL1之前,时钟的来源一直是pll1_main_clk,需要切去step_clk
但是step_clk也是可以选的,一般都选osc_clk也就是晶振的24MHz(注意,再设置PLL1之前应该先切step_clk到osc
改完PLL1之后需要切回去,切回去之后是1056,再调整刚刚上面的ARM_PODF位进行2分频
只用关注8和2,0为PLL3的
PFD频率
PFD0_FRAC
: PLL2_PFD0 的分频数,PLL到PFD需要分频,比如PLL2_PFD0 的计算公式为 528*18/PFD0_FRAC
,此为可设置的范围为12~35 。 如 果 PLL2_PFD0 的 频 率 要 设 置 为 352MHz 的话 PFD0_FRAC=528*18/352=27。
PFD0_STABLE
:只读位,看看PFD稳不稳定
PFD0_CLKGATE
:使能位,0时输出
AHB、IPG 和 PERCLK 根时钟设置
外设根时钟设置范围
初始化AHB_CLK_ROOT是必须的,PERCLK_CLK_ROOT和IPG_CLK_ROOT需要用到
中断系统
A7统一将外设的中断纳入到了IRQ中断里了
有个GIC控制器(Generic Interrupt Controller),我们用的是老版本V2,3-4给v8等等用的,V2最多支持两个核
ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ
这是V2的框图,左边是中断源,可以看到每个核心都有
中断分类
GIC把中断源分为三类
- SPI(share peripheral Interrupt,所有核共享的中断,
- PPI(private 。。。),私有中断,让那个核心自己搞掂
- SGI(Software-generated Interrupt,软件中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信
中断ID
ID0-15
:SGI
ID16-31
:PPI
ID32-1019
:SPI
6ULL 支持160个中断,被厂商定义了
CP15协处理器
CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有 16 个 32 位寄存器。CP15 协处理器的访问通过如下另个指令完成:
MRC
: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR
: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。
MRC 就是读 CP15 寄存器,MCR 就是写 CP15 寄存器,MCR 指令格式如下:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
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
在图 17.1.4.3 中当 MRC/MCR 指令中的 CRn=c1,opc1=0,CRm=c0,opc2=0 的时候就表示此时的 c1 就是 SCTLR 寄存器,也就是系统控制寄存器,这个是 c1 的基本作用。SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、I/D Cache 等
参考文档《Cortex-A7 Technical ReferenceManua.pdf》Chapter4 System Control,可以知道c0,c1,c12,c15等等的映射与功能表
中断使能
总的
单个的需要在GIC的GICD_ISENABLERn
和 GICD_ ICENABLERn
寄存器来完成外部中断的使能与禁止
其 中 GICDISENABLER0 的 bit[15:0] 对应 ID15~0 的 SGI 中断,GICDISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。
剩下的 GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。
中断优先级
GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高
A7有32个优先级,在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级
I.MX6U 是 Cortex-A7 内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000
抢占优先级和子优先级的位数设置
由GICC_BPR决定抢占优先级和子优先级各占多少位
移植SDKcore_ca7.h
/* IRQ中断!重点!!!!! */
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
mrs r0, spsr /* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
* Cortex-A7 Technical ReferenceManua.pdf P68 P138
*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
* 这个中断号来绝对调用哪个中断服务函数
*/
push {r0, r1} /* 保存r0,r1 */
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */
今日在理解这个中断的过程发现自己有东西漏了,过来猛补
可以得知中断是通过中断号来判断的,进一步发现system_irqhandler有个入参是能算出中断号的
但是,C/assembly是怎么把参数穿进去的?
推荐链接:从汇编语言的寄存器来看函数参数传递但是老哥的assembly是x86的吧?可能有点难看懂
数据传递、局部变量的分配和释放通过操纵程序栈来实现。为函数调用分配的那部分栈称为
栈帧
arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值 - thammer
可以知道的是,传参是通过r0为第一个参数开始的。
根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。
再看看我们的assembly
mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
* Cortex-A7 Technical ReferenceManua.pdf P68 P138
*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
用到了CP15将c0的数据提到r1了,这个是GIC的基地址,然后在加上0x2000就是CPU接口端的基地址,再加上0c就是IAR寄存器
IAR寄存器的前几位是中断号
移到r0,在跳转到system_irqhandler,&上前几位就是中断号(前几位是CPUID
处理完中断之后,还需要收尾工作
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
我们回到了IRQ模式,同时写入EOIR
什么意思?
通过高亮处我们得知,得到有效的终端号后,必须写入对应的中断ID From GICC_IAR
所以我们最后 str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
(r1是GICC基地址
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */
然后就是要恢复现场了,我们将原来lr(链接寄存器)出栈,然后将lr-4(取指部分)提出来
(不好评价,如果我不是取指呢?