跳到主要内容

I.MX6U学习笔记裸机篇

看了看廷廷的笔记,我感觉我的学习方法还是有点差劲,不知道效率怎么样,但是质量肯定是差

与其多次重复学习洗脑,不如教别人来的快

文件夹软连接

ls -s src dsn

Makefile

注意缩进为tab

赋值符

  1. 赋值符=:会默认用最晚改(定义)的那个变量

  2. :=:会默认用最开始定义的变量

  3. ?=:是一个很有用的赋值符,比如下面这行代码:

    curname ?= zuozhongkai

    如果变量 curname 前面没有被赋值,那么此变量就是“zuozhongkai”,如果前面已经赋过值了,那么就使用前面赋的值。

  4. +=:变量追加

自动化变量

image-20230724125839732

伪目标

伪目标不代表真正的目标名,在执行 make 命令通过指定伪目标,去执行自己想执行的命令。

如果说文件夹里也有同样的文件比如clean,因为clean没有依赖文件,make clean会认为clean是最新的,而Makefile里的命令执行不了

为了避免这个问题,我们可以将 clean 声明为伪目标,声明方式如下:

.PHONY : clean

I.MX6U 架构

这家伙与我们平时用的stm32也没有说有啥大的区别吧,学完这家伙之后可能转rsicv需要点时间,但是想把他学扎实点

这家伙用的是cortex-A系列的内核,A7,听说四个A7能有865一样性能

可以阅读ARM的手册

image-20230725140311076

没有实际应用过这几种模式,不好评,只知道每一种模式都有一组寄存器供异常处理程序使用,恢复

A7寄存器组

16 个 32 位的通用寄存器(R0~R15)供软件使用,前 15 个(R0~R14)可以用作通用的数据存储,R15 是程序计数器 PC,用来保存将要执行的指令。

ARM 还提供了一个当前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是 CPSR 寄存器的 备份

image-20230725140701726

image-20230725140857103

通用寄存器

R0~R15 就是通用寄存器,通用寄存器可以分为以下三类

  1. 未备份寄存器
  2. 备份寄存器
  3. 程序计数器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链接寄存器的用途:image-20230725160113395

R15程序计数器:学过计算机组成原理的应该了解过流水线,因为流水线的缘故,PC会比现在执行的地方加多8个字节

ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执行, 比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放 在 R15(PC)中。我们喜欢以当前正在执行的指令作为参考点,也就是以第一条指令为参考点, 那么 R15(PC)中存放的就是第三条指令,换句话说就是 R15(PC)总是指向当前正在执行的指令地址再加上 2 条指令的地址。对于 32 位的 ARM 处理器,每条指令是 4 个字节,所以:

R15 (PC)值 = 当前执行的程序位置 + 8 个字节。

程序状态寄存器

image-20230725161304387

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)位。

image-20230725161838758

总结

以上的,可能干看没啥感觉,因为stm32貌似就两种状态,而且也没有自己写操作系统

所以,大概看一下就好了,然后记得有这些标志位,可能不知道什么时候(比如说自己写rtos的时候切换模式?debug的时候打印寄存器?idk

IO初始化

  1. 使能时钟
  2. 设置IO复用
  3. 调整IO电气属性

随便抽取一位幸运IO来看看寄存器

image-20230725163538657

可以看到还是有很多的,注意PAD和MUX的区别

image-20230725163348845

我们主要聚焦于可能用的上的电气属性

PUS(bit15-14):push,设置上拉电阻

image-20230725163712187

ODE(bit11):对应图 8.1.4.2 中的 ODE,当 IO 作为输出的时候,此位用来禁止或者使能开路输出,此位为 0 的时候禁止开路输出,当此位为 1 的时候就使能开路输出功能。

SPEED(bit7:6):对应图 8.1.4.2 中的 SPEED,当 IO 用作输出的时候,此位用来设置 IO 速度,设置如表 8.1.4.2 所示:

image-20230725165119430

DSE(bit5:3):对应图 8.1.4.2 中的 DSE,当 IO 用作输出的时候用来设置 IO 的驱动能力,总共有 8 个可选选项,如表 8.1.4.3 所示:

image-20230725165224417

SRE(bit0):对应图 8.1.4.2 中的 SRE,设置压摆率,当此位为 0 的时候是低压摆率,当为 1 的时候是高压摆率。这里的压摆率就是 IO 电平跳变所需要的时间.如果你的产品要过 EMC 的话那就可以使用小的压摆率,因为波形缓和,如果你当前所使用的 IO 做高速通信的话就可以使用高压摆率。

通过上面的介绍,可以看出寄存器 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00 是用来配置 GPIO1_IO00 的,包括速度设置驱动能力设置压摆率设置等等。

image-20230725165744294

当 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。

  1. GIDR:IO输入输出

  2. PSR:pad status GPIO状态

  3. ICR1/2:中断控制寄存器

    ICR1 用于配置低 16 个 GPIO, ICR2 用于配置高 16 个 GPIO

    image-20230725172955785

  4. IMR:中断屏蔽寄存器

  5. ISR:中断状态寄存器

Chapter 18: Clock Controller Module(CCM)

CCM里面的外设时钟使能寄存器 。 CMM 有 CM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关

image-20230725173647267

image-20230725173658533

每个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 引脚

image-20230725195807294

FUSE是熔断,一般不用吧

01串行下载是从usb/串口下载

内部Boot模式

选好内部BOOT之后可以用GPIO控制BOOT rom选哪个来启动了,

image-20230725200559671

镜像烧写

imxdownload干了什么?idk

.imx组成:

  1. Image vector table:一系列地址向量
  2. Boot data :启动数据,包括镜像要拷去哪,大小
  3. Device configuration data:设备配置信息,DDR3配置等等
  4. 用户可执行文件bin

至今我们不知道为什么不从0x80000000开始

前面塞了一坨屎3KByte, load.imx 在 DDR 中的起始地址就是 0X87800000- 3072=0X877FF400

image-20230725205940722

里面有个self嗷,应该就是ivt了

第一个header:

image-20230725212018796

0X41

image-20230725212133199

image-20230725212226155

DCD数据

复位外设寄存器的默认值。DCD 区域不能超过 1768Byte,DCD 区域结构如图

image-20230725212619236

原文放送

image-20230725212756379

链接脚本

定义了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);
}
}

因为入参比较多,而且宏定义写好了,所以再提一下

  1. muxRegister:IO复用寄存器地址
  2. muxMode:IO复用值,就是ALT0-8,设置不同的复用功能
  3. inputRegister:外设输入IO选择寄存器地址,有些 IO 在设置为其他的复用功能以后还需要置 IO 输 入 寄 存 器 , 比如 GPIO1_IO03 要复用为 UART1_RX的话还需要设置寄存器UART1_RX_DATA_SELECT_INPUT,此寄存器地址为 0X020E0624
  4. inputDaisy:inputRegister的值
  5. configRegister:未使用,函数 IOMUXC_SetPinConfig 会使用这个寄存器
  6. 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时钟源

  1. ARM PLL:给ARM内核用的,可编程超到1.3GHzimage-20230726232649832

  2. 528_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 接口等等

    image-20230726232956355

  3. USB1_PLL(PLL3):USBPHY使用,同时也有4路PFD,固定20倍频=480MHz

  4. USB2_PLL(PLL7):同理

  5. ENET_PLL(PLL6):PLL为20+5/6倍频,500MHz,此PLL用于生成网络的时钟

  6. VIDEO_PLL(PLL5):用于显示的外设如LCD,PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频

  7. AUDIO_PLL(PLL4),此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整,PLL 的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选 1/2/4 分频。

image-20230727013357102

内核时钟设置

image-20230727103459932内核时钟来源于PLL1,而通过CACRR寄存器的ARM_PODF位可对其进行1248分频

498的主频=996/2

528主频=1056/2

696主频=696/1

注意

她最右边还有个分频对吧?假的,没有作用的哦

那怎么设置PLL1主频?再来看一下寄存器 CCM_ANALOG_PLL_ARMn

image-20230727105227098

  1. ENABLE:为开启时钟输出,0PLL1就shutdown了
  2. DIV_SELECT:pll输出频率,可以设置54-108,输出=FIN*value/2,FIN=24MHz,如果要让pll1输出为1056的话,这个寄存器要设为88

设置这家伙还好,修改PLL1的时候要切去其他时钟源

image-20230727105708167

可以看到2/3都有个CCSR:,这两个是CCSR中的两个位,用于控制时钟源

当我们调整PLL1之前,时钟的来源一直是pll1_main_clk,需要切去step_clk

但是step_clk也是可以选的,一般都选osc_clk也就是晶振的24MHz(注意,再设置PLL1之前应该先切step_clk到osc

改完PLL1之后需要切回去,切回去之后是1056,再调整刚刚上面的ARM_PODF位进行2分频

image-20230727110047252

只用关注8和2,0为PLL3的

PFD频率

image-20230727110933358

image-20230727111004055

image-20230727110907134

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时输出

image-20230727125527911

AHB、IPG 和 PERCLK 根时钟设置

外设根时钟设置范围

image-20230727145448771

初始化AHB_CLK_ROOT是必须的,PERCLK_CLK_ROOT和IPG_CLK_ROOT需要用到

image-20230727152710117

中断系统

image-20230728143433802

A7统一将外设的中断纳入到了IRQ中断里了

有个GIC控制器(Generic Interrupt Controller),我们用的是老版本V2,3-4给v8等等用的,V2最多支持两个核

ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ

image-20230728163831246

image-20230728163853282

这是V2的框图,左边是中断源,可以看到每个核心都有

中断分类

GIC把中断源分为三类

  1. SPI(share peripheral Interrupt,所有核共享的中断,
  2. PPI(private 。。。),私有中断,让那个核心自己搞掂
  3. SGI(Software-generated Interrupt,软件中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信

中断ID

ID0-15SGI

ID16-31PPI

ID32-1019SPI

6ULL 支持160个中断,被厂商定义了

image-20230728164503587

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

image-20230728172800640

image-20230728173308696

在图 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等等的映射与功能表

中断使能

总的

image-20230728180007383

单个的需要在GIC的GICD_ISENABLERnGICD_ 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 寄存器,此寄存器用来决定使用几级优先级

image-20230728180437119

image-20230728180551100

I.MX6U 是 Cortex-A7 内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000

抢占优先级和子优先级的位数设置

由GICC_BPR决定抢占优先级和子优先级各占多少位

image-20230728180629258

移植SDKcore_ca7.h

image-20230728194418711

/* 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

什么意思?

image-20230729112338701

通过高亮处我们得知,得到有效的终端号后,必须写入对应的中断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(取指部分)提出来

(不好评价,如果我不是取指呢?

image-20230729121512844