Cortex-A7裸机启动代码分析:以stm32MP135为例

31 篇文章 0 订阅
28 篇文章 0 订阅

资料准备

ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
stm32MP135裸机示例工程

0 前言

众所周知,在裸机程序在进入main函数之前都需要一份启动代码,这份启动代码基本都是汇编语言编写,为跳转到C语言运行环境做好准备工作。本文以基于Cortex-A7内核的stm32MP135为例,分析其裸机启动代码。

1 stm32MP135第一个执行的函数Reset_Handler

查看stm32MP135的裸机链接脚本,可以看到如下语句:

/* Entry Point */
ENTRY(Reset_Handler)

ENTRY(symbol)定义了应用程序执行的一条指令,symbol为所在的符号,也就意味着stm32MP135启动的第一条指令是Reset_Handler符号所在的地址

接着我们看看stm32MP135定义Reset_Handler的地址:
在这里插入图片描述
可以看到Reset_Handler函数被定义在内存最开始的位置。
查看.list链接文件可以看到Reset_Handler被定义在0x2ffe0000位置:
在这里插入图片描述
这个位置就是stm32MP135的SYSRAM的首地址:
在这里插入图片描述
这样一来,当stm32MP135的内部bootloader将PC指向SYSRAM首地址时,便会进入Reset_Handler函数。

2 stm32MP135启动代码分析:Reset_Handler函数

Reset_Handler函数是在.c文件中的内嵌汇编代码,源码如下。__asm__表示后面的代码为内嵌汇编,"__volatile__告诉编译器不要优化这块内嵌汇编代码。

/*----------------------------------------------------------------------------
  Reset Handler called on controller reset
 *----------------------------------------------------------------------------*/
void Reset_Handler(void) {
  __asm__ volatile(
  ".code 32                                         \n"
  /* Mask interrupts */
  "CPSID   if                                      \n"

  /* Put any cores other than 0 to sleep */
  "MRC     p15, 0, R0, c0, c0, 5                   \n"  /* Read MPIDR */
  "ANDS    R0, R0, #3                              \n"
  "goToSleep:                                      \n"
  "ITT  NE                                         \n"  /* Needed when in Thumb mode for following WFINE instruction */
  "WFINE                                           \n"
  "BNE     goToSleep                               \n"

  /* Reset SCTLR Settings */
  "MRC     p15, 0, R0, c1, c0, 0                   \n"  /* Read CP15 System Control register */
  "BIC     R0, R0, #(0x1 << 12)                    \n"  /* Clear I bit 12 to disable I Cache */
  "BIC     R0, R0, #(0x1 <<  2)                    \n"  /* Clear C bit  2 to disable D Cache */
  "BIC     R0, R0, #0x1                            \n"  /* Clear M bit  0 to disable MMU */
  "BIC     R0, R0, #(0x1 << 11)                    \n"  /* Clear Z bit 11 to disable branch prediction */
  "BIC     R0, R0, #(0x1 << 13)                    \n"  /* Clear V bit 13 to disable hivecs */
  "BIC     R0, R0, #(0x1 << 29)                    \n"  /* Clear AFE bit 29 to enable the full range of access permissions */
  "ORR     R0, R0, #(0x1 << 30)                    \n"  /* Set TE bit to take exceptions in Thumb mode */
  "MCR     p15, 0, R0, c1, c0, 0                   \n"  /* Write value back to CP15 System Control register */
  "ISB                                             \n"

  /* Configure ACTLR */
  "MRC     p15, 0, r0, c1, c0, 1                   \n"  /* Read CP15 Auxiliary Control Register */
  "ORR     r0, r0, #(1 <<  1)                      \n"  /* Enable L2 prefetch hint (UNK/WI since r4p1) */
  "MCR     p15, 0, r0, c1, c0, 1                   \n"  /* Write CP15 Auxiliary Control Register */

  /* Set Vector Base Address Register (VBAR) to point to this application's vector table */
  "LDR    R0, =Vectors                             \n"
  "MCR    p15, 0, R0, c12, c0, 0                   \n"
  "ISB                                             \n"

  /* Setup Stack for each exceptional mode */
  "CPS    %[fiq_mode]                              \n"
  "LDR    SP, =FIQ_STACK                           \n"
  "CPS    %[irq_mode]                              \n"
  "LDR    SP, =IRQ_STACK                           \n"
  "CPS    %[svc_mode]                              \n"
  "LDR    SP, =SVC_STACK                           \n"
  "CPS    %[abt_mode]                              \n"
  "LDR    SP, =ABT_STACK                           \n"
  "CPS    %[und_mode]                              \n"
  "LDR    SP, =UND_STACK                           \n"
  "CPS    %[sys_mode]                              \n"
  "LDR    SP, =SYS_STACK                           \n"

  /* Call SystemInit */
  "BL     SystemInit                               \n"

  /* Unmask interrupts */
  "CPSIE  if                                       \n"

  /* Initialize libc */
  "BL __libc_init_array \n"
  /* Call __main */
  "BL     main                                     \n"
  ::[usr_mode] "M" (USR_MODE),
    [fiq_mode] "M" (FIQ_MODE),
    [irq_mode] "M" (IRQ_MODE),
    [svc_mode] "M" (SVC_MODE),
    [abt_mode] "M" (ABT_MODE),
    [und_mode] "M" (UND_MODE),
    [sys_mode] "M" (SYS_MODE));
}

源码分析如下:
(1)指示后面指令均为ARM指令集

".code 32                                         \n"

表示后面的指令全部用ARM指令集。
(2)关闭IRQ和FIQ中断

"CPSID   if                                      \n"

CPSID指令用来改变处理器状态,具体来说就是失能中断。这里的if指的是IRQ和FIQ,意思就是失能IRQ和FIQ中断。
(3)将除0之外的所有核心置于睡眠状态

/* Put any cores other than 0 to sleep */
  "MRC     p15, 0, R0, c0, c0, 5                   \n"  /* Read MPIDR */
  "ANDS    R0, R0, #3                              \n"
  "goToSleep:                                      \n"
  "ITT  NE                                         \n"  /* Needed when in Thumb mode for following WFINE instruction */
  "WFINE                                           \n"
  "BNE     goToSleep                               \n"

Cortex-A内核支持多核心,stm32MP135是单核SoC,这里将其它没有用上的核心全部置于睡眠状态。
逐行分析:
(3.1)将MPIDR寄存器(多处理器关联寄存器)的值写入到寄存器R0

"MRC     p15, 0, R0, c0, c0, 5                   \n"  /* Read MPIDR */

MRC指令的语法如下所示:

MRC{cond} coproc, opc1, Rt, CRn, CRm{, opc2}
cond为条件码。
coproc为协处理器名称,CP0~CP15协处理器分别对应名称p0~p15。
opc1为协处理器要执行的操作码,取指范围为0~7。
Rt为ARM通用寄存器,用于存储读取到的协处理器寄存器数据。
CRn为协处理器寄存器,对于CP15协处理器来说,CRn取值范围为c0~c15。
CRm为协处理器寄存器,对于CP15协处理器来说,通过CRm和opc2一起来确定CRn对应的具体寄存器。
opc2为可选的协处理器执行操作码,取指范围为0~7,当不需要的时候要设置为0

在这里插入图片描述
(3.2)将MPIDR寄存器的值和0x3进行逻辑与,将结果保存到寄存器R0

"ANDS    R0, R0, #3                              \n"
ANDS是一个ARM指令,与and类似,但对两个操作数进行按位与运算,并将结果存储到第一个操作数中。例如:ands r1, r2, #0xff 表示将寄存器r2中的值和0xff进行按位与运算,然后将结果存储到寄存器r1中。ands影响z比特位,Z标志位用于表示两个操作数进行逻辑与操作后的结果是否为零,如果ANDS指令的操作数进行逻辑与操作后的结果为零,则Z标志位被设置为1,表示两个操作数相等。

(3.3)定义goToSleep函数

在汇编语言中,地址标号:用于标识内存单元的地址,它们在程序中的作用类似于高级语言的函数名或变量名。地址标号可以分为两种主要类型:地址标号和数据标号。

"ITT  NE                                         \n"

其实ITT指令的结构就是“IF-Then-Then”,语法都是由字母“T”和“E”构成。IT块中的每条指令必须指定相同或逻辑相反的条件后缀。意思是,如果使用NE,那么前两个指令必须有相同的后缀。 NE表示not equal(不相等)。这里判断MPIDR的值是否为0x3,如果不为0x3则会执行接下来的2行汇编代码。

"WFINE                                           \n"

WFI指令是‌ARM架构中用于将处理器置于等待中断状态的指令,主要用于低功耗模式。‌ 当处理器执行WFI指令时,它会停止执行当前程序,进入等待状态,直到接收到中断信号为止。

"BNE     goToSleep                               \n"

跳转到goToSleep标号(函数)处。
(4)重设系统控制寄存器(SCTLR)

/* Reset SCTLR Settings */
  "MRC     p15, 0, R0, c1, c0, 0                   \n"  /* Read CP15 System Control register */
  "BIC     R0, R0, #(0x1 << 12)                    \n"  /* Clear I bit 12 to disable I Cache */
  "BIC     R0, R0, #(0x1 <<  2)                    \n"  /* Clear C bit  2 to disable D Cache */
  "BIC     R0, R0, #0x1                            \n"  /* Clear M bit  0 to disable MMU */
  "BIC     R0, R0, #(0x1 << 11)                    \n"  /* Clear Z bit 11 to disable branch prediction */
  "BIC     R0, R0, #(0x1 << 13)                    \n"  /* Clear V bit 13 to disable hivecs */
  "BIC     R0, R0, #(0x1 << 29)                    \n"  /* Clear AFE bit 29 to enable the full range of access permissions */
  "ORR     R0, R0, #(0x1 << 30)                    \n"  /* Set TE bit to take exceptions in Thumb mode */
  "MCR     p15, 0, R0, c1, c0, 0                   \n"  /* Write value back to CP15 System Control register */
  "ISB                                             \n"

通过设置CPSR寄存器,将ICache、DCache、MMU、分支预测、向量基址设置为0x0、页表支持全部访问权限、在Thumb模式下处理异常/中断,最后使用指令同步屏障,保证所有操作执行完再执行下一条汇编代码。
有关CPSR寄存器的描述如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(5)配置辅助控制寄存器(ACTLR)

/* Configure ACTLR */
  "MRC     p15, 0, r0, c1, c0, 1                   \n"  /* Read CP15 Auxiliary Control Register */
  "ORR     r0, r0, #(1 <<  1)                      \n"  /* Enable L2 prefetch hint (UNK/WI since r4p1) */
  "MCR     p15, 0, r0, c1, c0, 1                   \n"  /* Write CP15 Auxiliary Control Register */

通过设置ACTLR寄存器,使能L2 prefetcher(预取)。打开L2 prefetcher功能后,当访问大片内存,同时Dcache中没有缓存时,可以提高访问速率。这个寄存器是IMPLEMENTATION DEFINED(简称 IMP DEF,由处理器设计厂商定义)寄存器,因此在ARMv7的参考手册并没有该寄存器描述。

(6)设置向量表基地址

/* Set Vector Base Address Register (VBAR) to point to this application's vector table */
  "LDR    R0, =Vectors                             \n"
  "MCR    p15, 0, R0, c12, c0, 0                   \n"
  "ISB                                             \n"

首先将向量表基地址Vectors写入寄存器R0,然后使用MCR指令,将向量表基地址写VBAR(向量基址寄存器),VBAR寄存器描述如下:
在这里插入图片描述
注意:
向量基址32字节对齐的。
(7)为不同工作模式设置堆栈指针

#define USR_MODE 0x10            /* User mode */
#define FIQ_MODE 0x11            /* Fast Interrupt Request mode */
#define IRQ_MODE 0x12            /* Interrupt Request mode */
#define SVC_MODE 0x13            /* Supervisor mode */
#define ABT_MODE 0x17            /* Abort mode */
#define UND_MODE 0x1B            /* Undefined Instruction mode */
#define SYS_MODE 0x1F            /* System mode */
/* Setup Stack for each exceptional mode */
  "CPS    %[fiq_mode]                              \n"
  "LDR    SP, =FIQ_STACK                           \n"
  "CPS    %[irq_mode]                              \n"
  "LDR    SP, =IRQ_STACK                           \n"
  "CPS    %[svc_mode]                              \n"
  "LDR    SP, =SVC_STACK                           \n"
  "CPS    %[abt_mode]                              \n"
  "LDR    SP, =ABT_STACK                           \n"
  "CPS    %[und_mode]                              \n"
  "LDR    SP, =UND_STACK                           \n"
  "CPS    %[sys_mode]                              \n"
  "LDR    SP, =SYS_STACK                           \n"

CPS指令用于切换当前CPU工作模式,切换到某种模式后,将对应的堆栈首地址写入SP堆栈指针,最后切换到的模式是系统模式。
在链接脚本内有所有模式栈底地址的定义:
在这里插入图片描述
在初始化了堆栈指针后我们便可以进入C语言的世界了。
(8)系统初始化

"BL     SystemInit                               \n"

跳转到SystemInit函数,这个函数使用C语言编写。主要工作如下:将.bss段全部设置为0(不需要对.data段操作,因为本例的.data段数据已经被GCC直接加载到RAM中。如果是使用ARM编译器,则在执行__main中的__scatterload会初始化data段、bss段,GCC编译器可以使用__libc_init_array对data段和bss段初始化)、初始化页表、清空所有中断/异常、初始化分支预测、初始化ICache、初始化DCache,如果定义了MMU_USE宏,则创建页表并使能MMU。如果定义了CACHE_USE宏则使能ICache、DCache。最后初始化GIC,设置中断向量。
有关GCC是怎样将data段放到镜像中(最终被stm32MP135内部bootloader加载到RAM)的可以参考下图:
在这里插入图片描述

(10)使能IRQ和FIQ中断

"CPSIE   if                                      \n"

CPSIE指令用来改变处理器状态,具体来说就是使能中断。这里的if指的是IRQ和FIQ,意思就是使能IRQ和FIQ中断。
(11)跳转到main函数

"BL     main                                     \n"

最后便是进入mian函数,执行用户代码。

3 总结

(1)Cortex-A7裸机启动代码负责清理环境,营造一个纯净的运行环境(失能中断、关闭Cache等),这在BOOT跳转到APP时非常有用。
(2)Cortex-A7裸机启动代码负责初始化各个工作模式下的堆栈指针,最后切换到系统模式。
(3)Cortex-A7裸机启动代码负责系统初始化,如Cache初始化、页表初始化及创建等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值