关于正点原子的alpha开发板的启动函数(汇编,自己的认识)

我傻逼了,这里的注释还是不要用;

全部换成

/* */

这里就分为两块,一部分是复位中断部分,第二部分就是IRQ部分(中断部分最重要)

我就围绕着两部分来展开我的认识

首先声明全局 .global_start

在 ARM 架构的程序中,_start 常常是 C 程序的 main 函数之前的汇编代码,它负责设置程序的初始状态,比如初始化堆栈指针、设置 BSS 段、调用 C/C++ 运行时初始化代码等,然后将控制权传递给 main 函数

不知道为什么视频里的清楚和设置bss段,后面的代码没有了

第一步:设置中断向量表

这里设置中断向量表的顺序是固定的,依照架构技术手册设置。

发生了哪个中断就把其地址加载给pc指针

;中断向量表
	ldr pc,=Reset_Handler
	ldr pc,=Undefined_Handler
	ldr pc,=SVC_Handler
	ldr pc,=PrefAbort_Handler
	ldr pc,=DataAbort_Handler
	ldr pc,=NotUsed_Handler
	ldr pc,=IRQ_Handler
	ldr pc,=FIQ_Handler

一共有8种中断类型,我们最关心的两种一个是系统复位中断,第二种是外设中断IRQ中断,其他的目前是不太关心。所以其实现也是一个死循环,如下

SVC_Handler:
ldr r0,=SVC_Handler
bx r0

第二步:实现复位中断函数和IRQ中断函数

关闭C1寄存器里的几个功能,主要是操作SCTLR寄存器

	mrc p15,0,r0,c1,c0,0 /* 读取CP15的C1寄存器到R0中   */
	bic r0,r0,#(1<<12)/*清除C1寄存器的bit12位(I位),关闭I Cache */
	bic r0,r0,#(1<<11) /*清除C1寄存器的bit11(Z位),关闭分支预测 */
	bic r0,r0,#(1<<2)/*清除C1寄存器的bit2(C位),关闭D Cache */
	bic r0,r0,#(1<<1)/*清除C1寄存器的bit1(A位),关闭对齐 */
	bic r0,r0,#(1<<0)/*清除C1寄存器的bit0(M位),关闭MMU */
	mcr p15,0,r0,c1,c0,0 /* 将r0寄存器中的值写入到CP15的C1寄存器中*/

配置完该寄存器后就开始设置中断向量表基地址,这几个中断的偏移是在基地址为0x0的基础上的,但是我们存放其位置是在0x87800000

		/* 中断向量表偏移*/
	ldr r0,=0x87800000
	dsb
	isb
	mcr p15,0,r0,c12,c0,0
	dsb
	isb

其中dsb和isb是为了保障操作完成。

mcr p15,0,r0,c12,c0,0

该段代码意思是从CP15协处理器c12存入这个偏移地址

而c12里的VBAR是一个用来存放中断向量偏移基地址的寄存器

初始化这八种中断情况下的pc堆栈指针了。

当然可以全部实现,也可以只实现我们需要的

/* 设置各个模式下的堆栈指针*/

	/* IRQ模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f)/*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x12)/*r0或上0x12,表示使用IRQ模式 */
	msr cpsr,r0 /*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80600000/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB*/
	/* SYS模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f) /*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x1f)/* r0或上0x1f,表示使用SYS模式*/
	msr cpsr,r0/*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80400000/* 设置IRQ模式下的栈首地址为0X80400000,大小为2MB*/
	/* IRQ模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f)/*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x13)/* r0或上0x13,表示使用SVC模式*/
	msr cpsr,r0/*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80200000/*设置SVC模式下的栈首地址为0X80200000,大小为2MB */

在 ARM 架构中,状态寄存器 `SPSR`(Saved Program Status Register)用于保存当前的程序状态信息,包括处理器模式。

以下是切换处理器模式的几种常见方法:

1. **使用 `CPS` 指令**:ARM 架构提供了 `CPS`(Change Processor State)指令,用于切换处理器模式。例如,`CPS #0x13` 将切换到 SVC(Supervisor)模式,其中 `0x13` 是模式值加上一个优先级级别。

2. **修改 `CPSR` 寄存器**:直接修改 `CPSR` 寄存器可以改变处理器模式,但这通常不是安全的编程实践,因为它可能影响当前的程序状态。

3. **使用 `MSR` 指令**:`MSR`(Move to Status Register)指令可以用来修改 `CPSR` 或 `SPSR` 的某些字段。例如,`MSR spsr_cxsf, r0` 可以将寄存器 `r0` 的值写入 `SPSR` 的控制和状态字段。

4. **异常和中断处理**:当异常或中断发生时,处理器会自动切换到相应的模式,并保存当前的 `CPSR` 到相应的 `SPSR`。处理完异常或中断后,处理器会从 `SPSR` 恢复 `CPSR` 的值,从而切换回原来的模式。

5. **使用 `BX` 指令**:在某些情况下,使用 `BX` 指令跳转到一个具有不同模式的代码位置也可以实现模式切换。被跳转的目标代码需要设置正确的模式。

`SPSR` 主要用于异常和中断处理中保存和恢复程序状态,而不是直接用来切换模式。在编写程序时,应该使用 `CPS` 指令或 `MSR` 指令来安全地切换处理器模式,并确保程序状态的正确性。直接修改 `SPSR` 可能会导致不可预测的行为,因为 `SPSR` 也包含了其他状态信息,如中断屏蔽位等。

SPSR寄存器用来改变状态的就是低5位,当然我们也可以直接用cps 加上那5位切换到对应模式

cps #0x13/*切换到SVC模式*/
ldr sp,=0x80600000/*设置堆栈指针*/

这样也许更简洁。

设置完堆栈指针就该跳转到 main函数了,但是前面的设置过程为保证安全会关闭全局中断,设置完后再打开,类似于freertos里的进入临界区

2.1 具体的复位函数实现

Reset_Handler:

	cpsid i				/* 关闭全局中断*/
	mrc p15,0,r0,c1,c0,0 /* 读取CP15的C1寄存器到R0中   */
	bic r0,r0,#(1<<12)/*清除C1寄存器的bit12位(I位),关闭I Cache */
	bic r0,r0,#(1<<11) /*清除C1寄存器的bit11(Z位),关闭分支预测 */
	bic r0,r0,#(1<<2)/*清除C1寄存器的bit2(C位),关闭D Cache */
	bic r0,r0,#(1<<1)/*清除C1寄存器的bit1(A位),关闭对齐 */
	bic r0,r0,#(1<<0)/*清除C1寄存器的bit0(M位),关闭MMU */
	mcr p15,0,r0,c1,c0,0 /* 将r0寄存器中的值写入到CP15的C1寄存器中*/

	/* 设置各个模式下的堆栈指针*/

	/* IRQ模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f)/*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x12)/*r0或上0x12,表示使用IRQ模式 */
	msr cpsr,r0 /*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80600000/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB*/
	/* SYS模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f) /*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x1f)/* r0或上0x1f,表示使用SYS模式*/
	msr cpsr,r0/*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80400000/* 设置IRQ模式下的栈首地址为0X80400000,大小为2MB*/
	/* IRQ模式*/
	mrs r0,spsr;
	bic r0,r0,#(0x1f)/*将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
	orr r0,r0,#(0x13)/* r0或上0x13,表示使用SVC模式*/
	msr cpsr,r0/*将r0 的数据写入到cpsr_c中 */
	ldr sp,=0x80200000/*设置SVC模式下的栈首地址为0X80200000,大小为2MB */

	cpsie i	/*打开全局中断 */
	b main/*跳转到main函数 */

然后就是最重要的IRQ中断函数的编写

2.2 具体IRQ函数的实现

首先保存lr寄存器的值,也就是保存现场,也就是入栈操作,把寄存器中的值保存进内存中去。

入栈 r0-r3,r12,因为这几个寄存器不会自动保存,要手动保存

然后就使spsr状态寄存器,它也不会自动保存

好了这几个寄存器全部压入栈内了,等于中断前的现场已经保存完整

进入协处理器CP15的C15经过操作数4的选择后最终我们选择读取CBAR寄存器的值

这个寄存器全名叫做配置基地址寄存器

我们为什么要得到该寄存器的值呢,因为我们要进一步得到GIC控制器的基地址。GIC的基地址存在CBAR里,这点是最重要的。没有CBAR的读取就找不到GIC的基地址

通过找到GIC的基地址,再经过偏移0x2000我们就得到GIC-CPU接口的基地址再偏移0xc我们得到了GIC-IAR寄存器的地址。这个GIC-IAR寄存器就是我们干这麽久最关键的地方。如果是知道这个偏移,那我们这样就更便捷,但是可读性就差了一些。

add r1,r1,#(0x200C);

 找到IAR寄存器后读取寄存器里的值我们就得到了CPUID和中断号,他们被存入r0寄存器

 得到中断号后我们的目的就得到了,就能找到对应的中断函数地址了。入栈r0,r1,把中断号和GIC-CPU接口的地址保存下来

切换到SVC模式,保存当前模式下的lr寄存器

r0,r1都用了,根据system_irqhandler得到入口地址存到r2寄存器内

blx跳到对应函数。

出栈SVC模式下的lr寄存器,切换到IRQ模式。出栈r0,r1。在不同的处理模式下出入栈lr都是成对的,它们拥有不完全公用的堆栈空间。这里的出栈入栈都是针对内存而言的,出栈就是内存到寄存器,入栈就使寄存器到内存。此刻r0存的是中断号,r1存的是GIC-CPU的基地址。把中断号存入GIC-CPU的基地址偏移0x10处的寄存器内表示中断执行完成,写EOIR 出栈r0恢复状态寄存器也就是

当前堆栈里还存有r0,r0-r3,r12的值,其中第一个r0存的是spsr寄存器的值

pop {r0}
msr spsr, r0

 这个与那个应该是等价的,都是接受栈内原本存入的spsr寄存器的值

然后恢复现场,还IRQ中断发生前的r0-r3,r12寄存器的内容

弹出lr-4的值给pc

这里我开始困惑的地方就是为什么没有-8,后来我才知道运行那段会被强制执行完,所以-4就可以了。

IRQ_Handler:
	push {lr}	/*保存当前运行地址 */
	push {r0-r3, r12}  /* 其他寄存器会自动保存,这几个要手动*/
	
	mrs r0 ,spsr  /*读取状态寄存器 */
	push {r0}  /*保存 */

	mrc p15,4,r1,c15,c0,0 /*读取CP15的CBAR寄存器 */
	add r1,r1,#(0x2000) /*基地址偏移0x2000得到GIC-CPU接口的基地址 */
	ldr r0,[r1,#(0xc)] /* 将GIC-CPU基地址再偏移0xc得到GICC-IAR寄存器的基地址,取该地址的值*/
	/*得到中断号及CPUID */
	push {r0,r1}
	cps #(0x13) /* 切换到SVC模式*/
	push {lr} /*保存svc模式下的lr寄存器 */
	ldr r2,=system_irqhandler  /* 加载C语言的IRQ中断处理函数*/
	blx r2 /*跳转到对应的IRQ中断函数 */

	pop {lr}
	cps #0x12 /* 进入IRQ模式*/
	pop {r0,r1}
	str r0,[r1,#(0x10)] /*将其中断ID号写入r0保存的地址中,也就是IAR基地址 */
	pop {r0}
	msr spsr_cxsf, r0 /*恢复状态寄存器 */

	pop {r0-r3,r12}
	pop {lr}
	subs pc,lr,#4
	

经过我的测试,把中断向量基地址的偏移放在 _start:下也是没有问题的。
 

相关推荐

  1. 单机启动/开机启动SpringBoot服务正确方式

    2024-07-12 18:46:03       42 阅读
  2. 找到自己前提是认识自己

    2024-07-12 18:46:03       23 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-12 18:46:03       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-12 18:46:03       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-12 18:46:03       45 阅读
  4. Python语言-面向对象

    2024-07-12 18:46:03       55 阅读

热门阅读

  1. MySQL SQL100道基础练习题

    2024-07-12 18:46:03       16 阅读
  2. tomcat

    tomcat

    2024-07-12 18:46:03      21 阅读
  3. 倾斜摄影实景模型到底能不能用

    2024-07-12 18:46:03       20 阅读
  4. 力扣题解(等差数列划分)

    2024-07-12 18:46:03       18 阅读
  5. ES6 Module 的语法(十二)

    2024-07-12 18:46:03       15 阅读
  6. 王者荣耀爬虫程序

    2024-07-12 18:46:03       19 阅读
  7. yarn的安装与配置 (秒懂yarn用法)

    2024-07-12 18:46:03       18 阅读
  8. 错误集1

    2024-07-12 18:46:03       16 阅读
  9. ES6 async 函数详解 (十)

    2024-07-12 18:46:03       19 阅读
  10. Linux下如何解压rar文件

    2024-07-12 18:46:03       19 阅读